gentle-pi 3.7.0 → 4.1.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 +155 -10
- package/assets/agents/gentle-ai-explore.md +13 -6
- package/assets/agents/gentle-ai-verify.md +27 -6
- package/assets/agents/gentle-ai-worker.md +13 -15
- 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 +75 -169
- package/assets/orchestrator-memory.md +12 -26
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-skills.md +1 -1
- 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 +30 -38
- 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/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +956 -50
- 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/bundled-install.md +397 -0
- package/docs/delegated-verification.md +27 -4
- package/docs/gentle-agents-activity.md +537 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +165 -51
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +3 -3
- package/docs/permission-modes.md +82 -0
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +364 -231
- package/docs/render-cache-performance.md +28 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +35 -0
- package/extensions/child-safety.ts +25 -0
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +1093 -326
- package/extensions/gentle-ai.ts +1636 -890
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +1619 -136
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +126 -20
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1424 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +254 -92
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +191 -58
- package/extensions/startup-banner.ts +71 -33
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +99 -0
- package/lib/agents-runner.ts +180 -107
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/auto-permission-classifier.ts +280 -0
- package/lib/auto-permission-runtime.ts +268 -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 +220 -0
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +195 -0
- package/lib/command-palette-catalog.ts +5 -10
- package/lib/command-palette.ts +25 -14
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-bundled.ts +517 -0
- package/lib/gentle-shell-launcher.ts +89 -27
- package/lib/gentle-shell-resume-hint.ts +176 -0
- 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/history-capture-policy.ts +95 -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 +5 -1
- package/lib/nan-provider.ts +249 -0
- package/lib/native-review-cli.ts +242 -180
- 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-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- 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/overlay-repaint.ts +26 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +73 -21
- package/lib/review-candidate-view.ts +115 -27
- 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-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +227 -56
- package/lib/review-sidebar-state.ts +224 -0
- 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 +527 -0
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +266 -0
- package/lib/session-worktree-registry.ts +87 -9
- package/lib/shell-bar.ts +215 -30
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +42 -14
- package/lib/shell-changes.ts +86 -71
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +83 -15
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +84 -31
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +70 -13
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +383 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +119 -0
- package/lib/visual-customize-view.ts +481 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +317 -0
- package/package.json +20 -8
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +518 -0
- package/runtime/gentle-shell-launcher.mjs +87 -25
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- 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 +239 -177
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +225 -54
- 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 +8 -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 +83 -35
- 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/mirror-odd-routing.mjs +2 -2
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +90 -0
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +54 -19
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +91 -38
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +16 -21
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +30 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +97 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +467 -93
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/auto-mode-invariants.test.ts +258 -0
- package/tests/auto-mode-sdk.test.ts +57 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-classifier.test.ts +283 -0
- package/tests/auto-permission-runtime.test.ts +196 -0
- package/tests/autonomous-guard.test.ts +69 -1
- 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 +146 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- 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 +87 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-context-files.test.ts +255 -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 +94 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +596 -0
- package/tests/command-palette.test.ts +43 -7
- 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 -78
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +171 -3
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +288 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +69 -0
- package/tests/gentle-agents.test.ts +2804 -346
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +21 -4
- 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-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +566 -62
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +493 -14
- package/tests/gentle-ai.test.ts +1427 -236
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +896 -115
- package/tests/gentle-shell-bundled.test.ts +1002 -0
- package/tests/gentle-shell-launcher.test.ts +333 -65
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- 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 +4216 -119
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +333 -10
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +49 -19
- 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 +2158 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +389 -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-authority.test.ts +16 -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 +481 -0
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +13 -1
- package/tests/native-review-cli.test.ts +239 -118
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +101 -4
- 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 +76 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
- package/tests/odd-routing-contract.test.ts +158 -50
- package/tests/orchestrator-budget.test.ts +43 -45
- 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 +8 -7
- 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/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +344 -123
- package/tests/packed-runner-owned-path.test.ts +80 -0
- 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 +10 -18
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/provider-defect-handoff.test.ts +7 -14
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +569 -187
- package/tests/rdd-aware-verification-contract.test.ts +103 -40
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +237 -21
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +399 -12
- package/tests/review-contract-prompt.test.ts +138 -44
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +866 -6
- package/tests/review-controller-workspace-root.test.ts +206 -6
- 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 +342 -4
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +221 -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 +14 -37
- 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-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +594 -26
- 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-sidebar-state.test.ts +402 -0
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +202 -0
- package/tests/runtime-harness.mjs +186 -800
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +451 -0
- 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-messaging-grants.test.ts +255 -0
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +381 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +134 -2
- package/tests/shell-bar.test.ts +422 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +135 -32
- package/tests/shell-changes.test.ts +61 -62
- package/tests/shell-prompt.test.ts +94 -2
- 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 +351 -22
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +207 -3
- package/tests/shell-usage-view.test.ts +28 -1
- package/tests/shell-usage.test.ts +108 -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 +357 -2
- package/tests/startup-banner.test.ts +246 -4
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -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 +107 -20
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/theme-customization.test.ts +72 -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-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +139 -0
- package/tests/visual-customize-view.test.ts +696 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +271 -0
- package/tests/yolo-mode-runtime.test.ts +188 -0
- package/tests/yolo-mode.test.ts +289 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
- /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
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
export type StartupUpdateChoice = "update" | "skip" | "omit" | "cancel";
|
|
4
|
+
type TerminalInput = Readable & { isRaw?: boolean; setRawMode?: (raw: boolean) => unknown };
|
|
5
|
+
type TerminalOutput = Writable & { columns?: number; rows?: number; isTTY?: boolean };
|
|
6
|
+
type UpdateOffer = { current: string; latest: string; notesUrl: string };
|
|
7
|
+
type PromptOptions = { input?: TerminalInput; output?: TerminalOutput; color?: boolean };
|
|
8
|
+
|
|
9
|
+
const choices: StartupUpdateChoice[] = ["update", "skip", "omit"];
|
|
10
|
+
const labels = ["Update now (gentle-shell upgrade)", "Not now", "Skip this version"];
|
|
11
|
+
|
|
12
|
+
function sanitize(value: string, limit: number): string {
|
|
13
|
+
// Bound work before parsing, remove whole escape sequences (including OSC titles),
|
|
14
|
+
// then remove remaining control and bidi characters. Never emit an OSC hyperlink.
|
|
15
|
+
return value.slice(0, 2048)
|
|
16
|
+
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\|$)/g, "")
|
|
17
|
+
.replace(/(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]/g, "")
|
|
18
|
+
.replace(/\x1b[ -/]*[@-~]/g, "")
|
|
19
|
+
.replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, "")
|
|
20
|
+
.slice(0, limit);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cells(char: string): number {
|
|
24
|
+
const code = char.codePointAt(0)!;
|
|
25
|
+
if (/\p{Mark}/u.test(char) || code === 0x200d || code === 0xfe0f) return 0;
|
|
26
|
+
return code >= 0x1100 && (
|
|
27
|
+
code <= 0x115f || code === 0x2329 || code === 0x232a ||
|
|
28
|
+
(code >= 0x2e80 && code <= 0xa4cf) || (code >= 0xac00 && code <= 0xd7a3) ||
|
|
29
|
+
(code >= 0xf900 && code <= 0xfaff) || (code >= 0xfe10 && code <= 0xfe6f) ||
|
|
30
|
+
(code >= 0xff00 && code <= 0xff60) || (code >= 0xffe0 && code <= 0xffe6) ||
|
|
31
|
+
code >= 0x1f000
|
|
32
|
+
) ? 2 : 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function wrap(text: string, width: number): string[] {
|
|
36
|
+
const lines: string[] = [];
|
|
37
|
+
let line = "";
|
|
38
|
+
let used = 0;
|
|
39
|
+
for (const original of text) {
|
|
40
|
+
const char = cells(original) > width ? "?" : original;
|
|
41
|
+
const size = cells(char);
|
|
42
|
+
if (used + size > width) {
|
|
43
|
+
lines.push(line);
|
|
44
|
+
line = "";
|
|
45
|
+
used = 0;
|
|
46
|
+
}
|
|
47
|
+
line += char;
|
|
48
|
+
used += size;
|
|
49
|
+
}
|
|
50
|
+
lines.push(line);
|
|
51
|
+
return lines;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A pre-Pi chooser: returns intent only; it never invokes upgrade or persists policy. */
|
|
55
|
+
export function promptStartupUpdate(offer: UpdateOffer, options: PromptOptions = {}): Promise<StartupUpdateChoice> {
|
|
56
|
+
const input = options.input ?? process.stdin;
|
|
57
|
+
const output = options.output ?? process.stdout;
|
|
58
|
+
const color = options.color ?? output.isTTY === true;
|
|
59
|
+
const rose = (text: string) => color ? `\x1b[38;2;255;118;195m${text}\x1b[39m` : text;
|
|
60
|
+
const current = sanitize(offer.current, 40);
|
|
61
|
+
const latest = sanitize(offer.latest, 40);
|
|
62
|
+
const notes = sanitize(offer.notesUrl, 160);
|
|
63
|
+
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const wasRaw = input.isRaw === true;
|
|
66
|
+
const wasFlowing = input.readableFlowing === true;
|
|
67
|
+
let rawAttempted = false;
|
|
68
|
+
let cursorHidden = false;
|
|
69
|
+
let selected = 1;
|
|
70
|
+
let done = false;
|
|
71
|
+
let failure: unknown;
|
|
72
|
+
let outcome: StartupUpdateChoice = "skip";
|
|
73
|
+
let pendingWrites = 0;
|
|
74
|
+
let settlementScheduled = false;
|
|
75
|
+
let escape = "";
|
|
76
|
+
let escapeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
77
|
+
let previousMenuLines = 0;
|
|
78
|
+
let previousWidth = 0;
|
|
79
|
+
|
|
80
|
+
function settle() {
|
|
81
|
+
if (!done || pendingWrites || settlementScheduled) return;
|
|
82
|
+
settlementScheduled = true;
|
|
83
|
+
// Writable callback errors can be followed by an error event. Keep our
|
|
84
|
+
// listener until that event is drained, including during cursor cleanup.
|
|
85
|
+
setImmediate(() => {
|
|
86
|
+
output.removeListener("error", onError);
|
|
87
|
+
if (failure) reject(failure);
|
|
88
|
+
else resolve(outcome);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function write(text: string) {
|
|
93
|
+
pendingWrites++;
|
|
94
|
+
try {
|
|
95
|
+
output.write(text, (error?: Error | null) => {
|
|
96
|
+
pendingWrites--;
|
|
97
|
+
if (error) onError(error);
|
|
98
|
+
settle();
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
pendingWrites--;
|
|
102
|
+
onError(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function finish(choice: StartupUpdateChoice, error?: unknown) {
|
|
107
|
+
if (error && !failure) failure = error;
|
|
108
|
+
if (done) return;
|
|
109
|
+
done = true;
|
|
110
|
+
outcome = choice;
|
|
111
|
+
clearTimeout(escapeTimer);
|
|
112
|
+
input.removeListener("data", onData);
|
|
113
|
+
input.removeListener("end", onEnd);
|
|
114
|
+
input.removeListener("close", onEnd);
|
|
115
|
+
input.removeListener("error", onError);
|
|
116
|
+
try {
|
|
117
|
+
if (rawAttempted) input.setRawMode?.(wasRaw);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
failure ??= error;
|
|
120
|
+
}
|
|
121
|
+
if (!wasFlowing) input.pause();
|
|
122
|
+
if (cursorHidden) write("\x1b[?25h");
|
|
123
|
+
settle();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function onError(error: unknown) {
|
|
127
|
+
if (!failure) failure = error;
|
|
128
|
+
finish("skip", error);
|
|
129
|
+
}
|
|
130
|
+
function onEnd() { finish("skip"); }
|
|
131
|
+
|
|
132
|
+
function terminalWidth(): number {
|
|
133
|
+
const width = output.columns;
|
|
134
|
+
return typeof width === "number" && Number.isFinite(width) ? Math.max(1, Math.min(76, Math.floor(width))) : 76;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function render(initial = false) {
|
|
138
|
+
const width = terminalWidth();
|
|
139
|
+
const bordered = width >= 6;
|
|
140
|
+
const inner = bordered ? width - 4 : width;
|
|
141
|
+
const row = (text: string) => wrap(text, inner).map(line => {
|
|
142
|
+
const used = [...line].reduce((sum, char) => sum + cells(char), 0);
|
|
143
|
+
return bordered ? `| ${line}${" ".repeat(Math.max(0, inner - used))} |` : line;
|
|
144
|
+
});
|
|
145
|
+
const border = bordered ? rose(`+${"-".repeat(width - 2)}+`) : "";
|
|
146
|
+
const menu = labels.flatMap((label, index) => row(`${index === selected ? ">" : " "} ${index + 1}. ${label}`));
|
|
147
|
+
if (bordered) menu.push(border);
|
|
148
|
+
let prefix = "";
|
|
149
|
+
if (initial) {
|
|
150
|
+
// Start on a fresh line so the existing shell prompt is never erased.
|
|
151
|
+
const header = [
|
|
152
|
+
...(bordered ? [border] : []),
|
|
153
|
+
...row("Gentle Shell update available").map(rose),
|
|
154
|
+
...row(`${current} -> ${latest}`),
|
|
155
|
+
...row("Release notes:"),
|
|
156
|
+
...row(notes),
|
|
157
|
+
...row("Arrows / 1-3: select; Enter: confirm"),
|
|
158
|
+
...row("Esc: not now; Ctrl+C: cancel"),
|
|
159
|
+
];
|
|
160
|
+
prefix = `\r\n${header.join("\r\n")}\r\n`;
|
|
161
|
+
} else {
|
|
162
|
+
const rows = output.rows ?? 24;
|
|
163
|
+
// On resize or a very short terminal, append rather than moving above
|
|
164
|
+
// our visible menu. Never clear a screen or enter an alternate screen.
|
|
165
|
+
if (width === previousWidth && previousMenuLines < rows) {
|
|
166
|
+
prefix = `\x1b[${previousMenuLines}A\r`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const clear = !initial && prefix.startsWith("\x1b[") ? "\x1b[2K" : "";
|
|
170
|
+
write(prefix + menu.map(line => `${clear}${line}\r\n`).join(""));
|
|
171
|
+
previousMenuLines = menu.length;
|
|
172
|
+
previousWidth = width;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function select(index: number) {
|
|
176
|
+
selected = (index + choices.length) % choices.length;
|
|
177
|
+
render();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function onData(chunk: Buffer | string) {
|
|
181
|
+
for (const char of chunk.toString()) {
|
|
182
|
+
if (done) break;
|
|
183
|
+
if (char === "\x03") { finish("cancel"); break; }
|
|
184
|
+
if (escape) {
|
|
185
|
+
clearTimeout(escapeTimer);
|
|
186
|
+
escape += char;
|
|
187
|
+
if (escape === "\x1b[" || escape === "\x1bO") {
|
|
188
|
+
escapeTimer = setTimeout(() => finish("skip"), 40);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (/^\x1b[\[O][ABCD]$/.test(escape)) {
|
|
192
|
+
select(selected + (char === "A" || char === "D" ? -1 : 1));
|
|
193
|
+
escape = "";
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
finish("skip");
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
if (char === "\x1b") {
|
|
200
|
+
escape = char;
|
|
201
|
+
escapeTimer = setTimeout(() => finish("skip"), 40);
|
|
202
|
+
} else if (/^[123]$/.test(char)) select(Number(char) - 1);
|
|
203
|
+
else if (char === "\r" || char === "\n") finish(choices[selected]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
output.on("error", onError);
|
|
208
|
+
input.on("error", onError);
|
|
209
|
+
input.on("end", onEnd);
|
|
210
|
+
input.on("close", onEnd);
|
|
211
|
+
try {
|
|
212
|
+
if (input.readableEnded || input.destroyed || output.destroyed) {
|
|
213
|
+
finish("skip");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (input.setRawMode) {
|
|
217
|
+
rawAttempted = true;
|
|
218
|
+
input.setRawMode(true);
|
|
219
|
+
}
|
|
220
|
+
cursorHidden = true;
|
|
221
|
+
write("\x1b[?25l");
|
|
222
|
+
if (!done) render(true);
|
|
223
|
+
if (!done) {
|
|
224
|
+
input.on("data", onData);
|
|
225
|
+
input.resume();
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
finish("skip", error);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { closeSync, constants, fstatSync, openSync, readSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
4
|
+
import { stripTerminalSequences, visibleWidth } from "@earendil-works/pi-tui";
|
|
5
|
+
|
|
6
|
+
const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
7
|
+
|
|
8
|
+
const FALLBACK_COLUMNS = 24;
|
|
9
|
+
const MAX_SNAPSHOT_BYTES = 256 * 1024;
|
|
10
|
+
|
|
11
|
+
/** Read only a bounded local snapshot, never poll Herdr's CLI/socket for geometry. */
|
|
12
|
+
export function createActivityColumns(socket: string, now: () => number = Date.now): () => number {
|
|
13
|
+
const path = isAbsolute(socket) ? join(dirname(socket), "session.json") : undefined;
|
|
14
|
+
let refreshAt = -Infinity;
|
|
15
|
+
let columns = FALLBACK_COLUMNS;
|
|
16
|
+
return () => {
|
|
17
|
+
const time = now();
|
|
18
|
+
if (time < refreshAt) return columns;
|
|
19
|
+
refreshAt = time + 5000;
|
|
20
|
+
columns = FALLBACK_COLUMNS;
|
|
21
|
+
if (!path) return columns;
|
|
22
|
+
let fd: number | undefined;
|
|
23
|
+
try {
|
|
24
|
+
// Reject FIFOs after opening without waiting for a writer; unsupported flags are omitted.
|
|
25
|
+
fd = openSync(path, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
|
|
26
|
+
const stat = fstatSync(fd);
|
|
27
|
+
if (!stat.isFile() || stat.size > MAX_SNAPSHOT_BYTES) return columns;
|
|
28
|
+
// One extra byte rejects files that grew after fstat, without unlimited reads.
|
|
29
|
+
const buffer = Buffer.alloc(MAX_SNAPSHOT_BYTES + 1);
|
|
30
|
+
const size = readSync(fd, buffer, 0, buffer.length, 0);
|
|
31
|
+
if (size > MAX_SNAPSHOT_BYTES) return columns;
|
|
32
|
+
const snapshot = JSON.parse(buffer.toString("utf8", 0, size));
|
|
33
|
+
const width: unknown = snapshot?.sidebar_width;
|
|
34
|
+
if (typeof width === "number" && Number.isInteger(width) && width >= 6 && width <= 512) {
|
|
35
|
+
// Divider + possible scrollbar + three-space agent detail prefix.
|
|
36
|
+
columns = width - 5;
|
|
37
|
+
}
|
|
38
|
+
} catch { /* Missing/malformed snapshots use conservative geometry. */ }
|
|
39
|
+
finally {
|
|
40
|
+
try { if (fd !== undefined) closeSync(fd); } catch { /* Best effort close. */ }
|
|
41
|
+
}
|
|
42
|
+
return columns;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Fit whole graphemes within cell, UTF-8 byte and Unicode scalar budgets. */
|
|
47
|
+
function fit(parts: string[], columns: number, bytes: number, characters: number, preferWords = true): number {
|
|
48
|
+
let cells = 0;
|
|
49
|
+
let used = 0;
|
|
50
|
+
let scalars = 0;
|
|
51
|
+
let count = 0;
|
|
52
|
+
let space = -1;
|
|
53
|
+
for (const part of parts) {
|
|
54
|
+
const width = visibleWidth(part);
|
|
55
|
+
const size = Buffer.byteLength(part);
|
|
56
|
+
const length = Array.from(part).length;
|
|
57
|
+
if (cells + width > columns || used + size > bytes || scalars + length > characters) break;
|
|
58
|
+
if (part === " ") space = count;
|
|
59
|
+
cells += width;
|
|
60
|
+
used += size;
|
|
61
|
+
scalars += length;
|
|
62
|
+
count++;
|
|
63
|
+
}
|
|
64
|
+
if (preferWords && count < parts.length && parts[count] !== " " && space > 0) return space;
|
|
65
|
+
return count;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Only the active Todo title is free text. LF is private framing, not a token. */
|
|
69
|
+
export function activitySummary(tasks: unknown, columns = FALLBACK_COLUMNS): string | null {
|
|
70
|
+
if (!Array.isArray(tasks)) return null;
|
|
71
|
+
const title: unknown = tasks.find((task) => task?.status === "in_progress")?.title;
|
|
72
|
+
if (typeof title !== "string") return null;
|
|
73
|
+
const clipped = title.length > 4096;
|
|
74
|
+
// Bound sanitization/segmentation work, preserve ZWJ emoji but strip bidi controls.
|
|
75
|
+
/* eslint-disable no-control-regex */
|
|
76
|
+
const clean = stripTerminalSequences(title.slice(0, 4096))
|
|
77
|
+
.replace(/[\x00-\x1f\x7f-\x9f\u061c\u200b-\u200c\u200e-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/g, " ")
|
|
78
|
+
.replace(/\s+/g, " ").trim();
|
|
79
|
+
/* eslint-enable no-control-regex */
|
|
80
|
+
if (!clean) return null;
|
|
81
|
+
const parts = Array.from(graphemes.segment(clean), ({ segment }) => segment);
|
|
82
|
+
// The bounded sample may end inside a grapheme; never publish that fragment.
|
|
83
|
+
if (clipped) parts.pop();
|
|
84
|
+
columns = Number.isFinite(columns) ? Math.max(1, Math.min(512, Math.floor(columns))) : FALLBACK_COLUMNS;
|
|
85
|
+
if (columns <= 2) return columns === 1 ? "…" : "◐…";
|
|
86
|
+
// Herdr caps each token at 80 scalars; count our icon/indent before its trim.
|
|
87
|
+
const whole = fit(parts, columns - 2, 252, 78, false);
|
|
88
|
+
if (!clipped && whole === parts.length) return `◐ ${parts.join("")}`;
|
|
89
|
+
// Reserve the second-row indent and ellipsis; private LF framing is not a token byte.
|
|
90
|
+
let firstCount = fit(parts, columns - 2, 247, 78);
|
|
91
|
+
if (!firstCount) return columns === 3 ? "◐…" : "◐ …";
|
|
92
|
+
const restFits = (count: number): boolean => {
|
|
93
|
+
const first = `◐ ${parts.slice(0, count).join("").trimEnd()}`;
|
|
94
|
+
const rest = parts.slice(count);
|
|
95
|
+
while (rest[0] === " ") rest.shift();
|
|
96
|
+
return fit(rest, columns - 2, 256 - Buffer.byteLength(first) - 2, 78, false) === rest.length;
|
|
97
|
+
};
|
|
98
|
+
// Prefer words unless splitting one would preserve the entire two-row title.
|
|
99
|
+
if (!clipped && !restFits(firstCount)) {
|
|
100
|
+
const hardCount = fit(parts, columns - 2, 247, 78, false);
|
|
101
|
+
if (restFits(hardCount)) firstCount = hardCount;
|
|
102
|
+
}
|
|
103
|
+
const first = `◐ ${parts.slice(0, firstCount).join("").trimEnd()}`;
|
|
104
|
+
const rest = parts.slice(firstCount);
|
|
105
|
+
while (rest[0] === " ") rest.shift();
|
|
106
|
+
const bytes = 256 - Buffer.byteLength(first) - 2; // Two-space indent.
|
|
107
|
+
let count = fit(rest, columns - 2, bytes, 78, false);
|
|
108
|
+
const overflow = clipped || count < rest.length;
|
|
109
|
+
if (overflow) count = fit(rest, columns - 3, bytes - 3, 77);
|
|
110
|
+
const second = rest.slice(0, count).join("").trimEnd();
|
|
111
|
+
return `${first}\n ${second}${overflow ? "…" : ""}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function metadataArgs(pane: string, summary: string | null, seq: number): string[] {
|
|
115
|
+
const rows = summary?.split("\n") ?? [];
|
|
116
|
+
const tokens = ["summary", "summary2"].flatMap((token, index) => rows[index]
|
|
117
|
+
? ["--token", `${token}=${rows[index]}`] : ["--clear-token", token]);
|
|
118
|
+
return ["pane", "report-metadata", pane, "--source", "gentle:activity", "--agent", "pi",
|
|
119
|
+
"--seq", String(seq), "--ttl-ms", "30000", ...tokens];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function metadataTransport(env: NodeJS.ProcessEnv): (summary: string | null, seq: number) => Promise<void> {
|
|
123
|
+
return (summary, seq) => new Promise((resolve) => {
|
|
124
|
+
// No shell, bounded output/time, and failure never blocks Pi's handlers.
|
|
125
|
+
execFile(env.HERDR_BIN_PATH ?? "herdr", metadataArgs(env.HERDR_PANE_ID!, summary, seq), {
|
|
126
|
+
env, timeout: 1500, maxBuffer: 4096, windowsHide: true,
|
|
127
|
+
}, () => resolve());
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** One in flight, one pending value; a clear supersedes queued active work. */
|
|
132
|
+
export class ActivityPublisher {
|
|
133
|
+
private pending: string | null | undefined;
|
|
134
|
+
private last: string | null | undefined;
|
|
135
|
+
private running = false;
|
|
136
|
+
private closed = false;
|
|
137
|
+
private cancel: (() => void) | undefined;
|
|
138
|
+
private seq = Date.now();
|
|
139
|
+
private readonly send: (summary: string | null, seq: number) => Promise<void>;
|
|
140
|
+
private readonly schedule: (fn: () => void) => () => void;
|
|
141
|
+
constructor(send: (summary: string | null, seq: number) => Promise<void>,
|
|
142
|
+
schedule: (fn: () => void) => () => void = (fn) => {
|
|
143
|
+
const timer = setTimeout(fn, 150);
|
|
144
|
+
timer.unref();
|
|
145
|
+
return () => clearTimeout(timer);
|
|
146
|
+
}) {
|
|
147
|
+
this.send = send;
|
|
148
|
+
this.schedule = schedule;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
update(summary: string | null, force = false): void {
|
|
152
|
+
if (this.closed || (!force && (this.pending !== undefined
|
|
153
|
+
? summary === this.pending : !this.running && summary === this.last))) return;
|
|
154
|
+
this.pending = summary;
|
|
155
|
+
if (summary === null) {
|
|
156
|
+
this.cancel?.();
|
|
157
|
+
this.cancel = undefined;
|
|
158
|
+
if (!this.running) void this.flush();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.enqueue();
|
|
162
|
+
}
|
|
163
|
+
private enqueue(): void {
|
|
164
|
+
if (this.running || this.cancel || this.pending === undefined) return;
|
|
165
|
+
this.cancel = this.schedule(() => { this.cancel = undefined; void this.flush(); });
|
|
166
|
+
}
|
|
167
|
+
private async flush(): Promise<void> {
|
|
168
|
+
if (this.pending === undefined) return;
|
|
169
|
+
const summary = this.pending;
|
|
170
|
+
this.pending = undefined;
|
|
171
|
+
this.running = true;
|
|
172
|
+
try { await this.send(summary, ++this.seq); this.last = summary; }
|
|
173
|
+
catch { /* Best effort; a refresh retries active state. */ }
|
|
174
|
+
finally { this.running = false; this.enqueue(); }
|
|
175
|
+
}
|
|
176
|
+
close(): void {
|
|
177
|
+
if (this.closed) return;
|
|
178
|
+
this.update(null, true);
|
|
179
|
+
this.closed = true;
|
|
180
|
+
this.cancel?.(); this.cancel = undefined;
|
|
181
|
+
if (!this.running) void this.flush();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { gentlePiConfigHome } from "./agent-home.ts";
|
|
5
|
+
|
|
6
|
+
// Prompt history capture is default-off. An explicit GENTLE_PI_HISTORY_CAPTURE
|
|
7
|
+
// value wins, then the Gentle → Customize preference, then off. Any missing,
|
|
8
|
+
// malformed or unreadable preference fails closed. Turning capture off only
|
|
9
|
+
// stops new captures; stored history is never deleted here.
|
|
10
|
+
export const HISTORY_CAPTURE_POLICY = { ON: "on", OFF: "off" } as const;
|
|
11
|
+
export type HistoryCapturePolicy = (typeof HISTORY_CAPTURE_POLICY)[keyof typeof HISTORY_CAPTURE_POLICY];
|
|
12
|
+
export const HISTORY_CAPTURE_SCHEMA = "gentle-pi.history-capture/v1";
|
|
13
|
+
export const HISTORY_CAPTURE_ENV = "GENTLE_PI_HISTORY_CAPTURE";
|
|
14
|
+
interface HistoryCaptureOptions { gentlePiConfigHome?: string }
|
|
15
|
+
export interface HistoryCapturePolicyResolution {
|
|
16
|
+
policy: HistoryCapturePolicy;
|
|
17
|
+
source: "global_file" | "default";
|
|
18
|
+
malformed: boolean;
|
|
19
|
+
globalFile: string;
|
|
20
|
+
}
|
|
21
|
+
export interface HistoryCaptureResolution {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
source: "env" | "global_file" | "default";
|
|
24
|
+
/** The persisted Customize preference, reported even while the env overrides it. */
|
|
25
|
+
preference: HistoryCapturePolicy;
|
|
26
|
+
envOverride: HistoryCapturePolicy | undefined;
|
|
27
|
+
malformed: boolean;
|
|
28
|
+
globalFile: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Only explicit on/off values override; anything else defers to the preference. */
|
|
32
|
+
export function historyCaptureEnvOverride(env: NodeJS.ProcessEnv = process.env): HistoryCapturePolicy | undefined {
|
|
33
|
+
const value = env[HISTORY_CAPTURE_ENV]?.trim().toLowerCase();
|
|
34
|
+
if (value === "1" || value === "true" || value === "on") return "on";
|
|
35
|
+
if (value === "0" || value === "false" || value === "off") return "off";
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function parseHistoryCaptureFile(raw: string): HistoryCapturePolicy | undefined {
|
|
40
|
+
try {
|
|
41
|
+
const value: unknown = JSON.parse(raw);
|
|
42
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
43
|
+
if (Object.keys(value).length !== 2 || !("schema" in value) || value.schema !== HISTORY_CAPTURE_SCHEMA || !("policy" in value)) return undefined;
|
|
44
|
+
return value.policy === "on" || value.policy === "off" ? value.policy : undefined;
|
|
45
|
+
} catch { return undefined; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveHistoryCapturePolicy(options: HistoryCaptureOptions = {}): HistoryCapturePolicyResolution {
|
|
49
|
+
const globalFile = join(options.gentlePiConfigHome ?? gentlePiConfigHome(), "history-capture.json");
|
|
50
|
+
try {
|
|
51
|
+
const policy = parseHistoryCaptureFile(readFileSync(globalFile, "utf8"));
|
|
52
|
+
return { policy: policy ?? "off", source: "global_file", malformed: policy === undefined, globalFile };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const missing = typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
55
|
+
return { policy: "off", source: missing ? "default" : "global_file", malformed: !missing, globalFile };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Full resolution for status surfaces; always reads the preference. */
|
|
60
|
+
export function resolveHistoryCapture(options: { env?: NodeJS.ProcessEnv; gentlePiConfigHome?: string } = {}): HistoryCaptureResolution {
|
|
61
|
+
const env = options.env ?? process.env;
|
|
62
|
+
const saved = resolveHistoryCapturePolicy({ gentlePiConfigHome: options.gentlePiConfigHome ?? gentlePiConfigHome(env) });
|
|
63
|
+
const envOverride = historyCaptureEnvOverride(env);
|
|
64
|
+
return {
|
|
65
|
+
enabled: (envOverride ?? saved.policy) === "on",
|
|
66
|
+
source: envOverride ? "env" : saved.source,
|
|
67
|
+
preference: saved.policy,
|
|
68
|
+
envOverride,
|
|
69
|
+
malformed: saved.malformed,
|
|
70
|
+
globalFile: saved.globalFile,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Per-prompt gate: an explicit env value never reads the preference file. */
|
|
75
|
+
export function historyCaptureEnabled(options: { env?: NodeJS.ProcessEnv; gentlePiConfigHome?: string } = {}): boolean {
|
|
76
|
+
const env = options.env ?? process.env;
|
|
77
|
+
const override = historyCaptureEnvOverride(env);
|
|
78
|
+
if (override) return override === "on";
|
|
79
|
+
return resolveHistoryCapturePolicy({ gentlePiConfigHome: options.gentlePiConfigHome ?? gentlePiConfigHome(env) }).policy === "on";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function writeHistoryCapturePolicy(policy: HistoryCapturePolicy, options: HistoryCaptureOptions = {}): string {
|
|
83
|
+
if (policy !== "on" && policy !== "off") throw new TypeError("Invalid history capture policy");
|
|
84
|
+
const home = options.gentlePiConfigHome ?? gentlePiConfigHome();
|
|
85
|
+
const path = join(home, "history-capture.json");
|
|
86
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
87
|
+
mkdirSync(home, { recursive: true });
|
|
88
|
+
try {
|
|
89
|
+
writeFileSync(temporary, `${JSON.stringify({ schema: HISTORY_CAPTURE_SCHEMA, policy })}\n`, { flag: "wx", mode: 0o600 });
|
|
90
|
+
renameSync(temporary, path);
|
|
91
|
+
} finally {
|
|
92
|
+
try { unlinkSync(temporary); } catch { /* Rename consumed the temporary file. */ }
|
|
93
|
+
}
|
|
94
|
+
return path;
|
|
95
|
+
}
|
|
@@ -73,6 +73,8 @@ export interface InProcessReviewerRequest {
|
|
|
73
73
|
readonly selection: string;
|
|
74
74
|
/** Routing thinking label: off | minimal | low | medium | high | xhigh | max. Omitted is treated as "off". */
|
|
75
75
|
readonly thinking?: string;
|
|
76
|
+
/** Effective Pi transport preference; absent preserves the provider default. */
|
|
77
|
+
readonly transport?: SimpleStreamOptions["transport"];
|
|
76
78
|
/** Frozen Go-materialized prompt bytes, submitted verbatim as the one user message. */
|
|
77
79
|
readonly prompt: Buffer;
|
|
78
80
|
readonly timeoutMs: number;
|
|
@@ -272,6 +274,7 @@ export async function runInProcessReviewer(request: InProcessReviewerRequest, de
|
|
|
272
274
|
...(auth.apiKey === undefined ? {} : { apiKey: auth.apiKey }),
|
|
273
275
|
...(auth.headers === undefined && attributionHeaders === undefined ? {} : { headers: attributionHeaders === undefined ? auth.headers : { ...attributionHeaders, ...auth.headers } }),
|
|
274
276
|
...(reasoning.reasoning === undefined ? {} : { reasoning: reasoning.reasoning }),
|
|
277
|
+
...(request.transport === undefined ? {} : { transport: request.transport }),
|
|
275
278
|
};
|
|
276
279
|
|
|
277
280
|
// An abort is classified by which signal actually fired, never by the
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Session-scoped display data only; never command output or process control. */
|
|
2
|
+
export const JOBS_SIDEBAR_EVENT = "gentle-ai:jobs-sidebar";
|
|
3
|
+
export const JOBS_STATUS_KEY = "gentle-jobs";
|
|
4
|
+
|
|
5
|
+
export interface RunningJobDisplay {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
startedAt: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface JobsSidebarSnapshot {
|
|
12
|
+
jobs: RunningJobDisplay[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isJobsSidebarSnapshot(value: unknown): value is JobsSidebarSnapshot {
|
|
16
|
+
if (!value || typeof value !== "object" || !Array.isArray((value as JobsSidebarSnapshot).jobs)) return false;
|
|
17
|
+
return (value as JobsSidebarSnapshot).jobs.every((job) => job && typeof job === "object" &&
|
|
18
|
+
typeof job.id === "string" && job.id.length > 0 && typeof job.label === "string" &&
|
|
19
|
+
typeof job.startedAt === "number" && Number.isFinite(job.startedAt));
|
|
20
|
+
}
|