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
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { createAgentSession, DefaultResourceLoader, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { createAssistantMessageEventStream, getCurrentTools, type AssistantMessage } from "@earendil-works/pi-ai";
|
|
9
|
+
import { createChildContextExtension } from "../extensions/child-context.ts";
|
|
10
|
+
import { REQUESTED_TOOLS_ENV } from "../lib/agents-runner.ts";
|
|
11
|
+
|
|
12
|
+
const requested = "read,grep,find,codegraph,subagent_parent_message";
|
|
13
|
+
|
|
14
|
+
function fixture(env: NodeJS.ProcessEnv, existing?: object) {
|
|
15
|
+
const tools = new Map<string, object>();
|
|
16
|
+
if (existing) tools.set("codegraph", existing);
|
|
17
|
+
const hooks = new Map<string, () => unknown>();
|
|
18
|
+
createChildContextExtension(env)({
|
|
19
|
+
on(name: string, handler: () => unknown) { hooks.set(name, handler); },
|
|
20
|
+
getAllTools() { return [...tools.keys()].map(name => ({ name })); },
|
|
21
|
+
registerTool(tool: { name: string }) { assert.equal(tools.has(tool.name), false); tools.set(tool.name, tool); },
|
|
22
|
+
} as unknown as ExtensionAPI);
|
|
23
|
+
return { tools, start: () => hooks.get("session_start")?.() };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test("fallback child context supplies explicitly requested CodeGraph without extra tools", () => {
|
|
27
|
+
const f = fixture({ GENTLE_PI_AGENTS_CHILD: "1", [REQUESTED_TOOLS_ENV]: requested });
|
|
28
|
+
f.start();
|
|
29
|
+
assert.deepEqual([...f.tools.keys()], ["codegraph"]);
|
|
30
|
+
const tool = f.tools.get("codegraph") as { parameters: { additionalProperties: boolean; properties: object } };
|
|
31
|
+
assert.equal(tool.parameters.additionalProperties, false);
|
|
32
|
+
assert.deepEqual(Object.keys(tool.parameters.properties), ["operation", "query", "limit"]);
|
|
33
|
+
f.start();
|
|
34
|
+
assert.equal(f.tools.size, 1, "repeated startup cannot register twice");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("fallback never overwrites package CodeGraph or grants it without explicit child scope", () => {
|
|
38
|
+
const existing = { identity: "package-owned implementation" };
|
|
39
|
+
const full = fixture({ GENTLE_PI_AGENTS_CHILD: "1", [REQUESTED_TOOLS_ENV]: requested }, existing);
|
|
40
|
+
full.start();
|
|
41
|
+
assert.equal(full.tools.get("codegraph"), existing);
|
|
42
|
+
for (const env of [{ [REQUESTED_TOOLS_ENV]: requested }, { GENTLE_PI_AGENTS_CHILD: "1" }, { GENTLE_PI_AGENTS_CHILD: "1", [REQUESTED_TOOLS_ENV]: "read,grep,find" }]) {
|
|
43
|
+
const f = fixture(env); f.start(); assert.equal(f.tools.size, 0);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("actual SDK child first model request includes CodeGraph and keeps selected model", async () => {
|
|
48
|
+
const root = mkdtempSync(join(tmpdir(), "child-codegraph-sdk-"));
|
|
49
|
+
const agentDir = join(root, "agent"); mkdirSync(agentDir);
|
|
50
|
+
const settingsManager = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
|
|
51
|
+
const runtime = await ModelRuntime.create({ authPath: join(agentDir, "offline-auth.json"), modelsPath: null, refreshOnCreate: false });
|
|
52
|
+
const observed: { model: string; tools: string[] }[] = [];
|
|
53
|
+
runtime.registerProvider("offline-explore", {
|
|
54
|
+
api: "offline-explore-test", apiKey: "offline-dummy", baseUrl: "http://offline.invalid",
|
|
55
|
+
models: [{ id: "configured-small", name: "Configured small fixture", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 100000, maxTokens: 1000 }],
|
|
56
|
+
streamSimple(model, context) {
|
|
57
|
+
observed.push({ model: model.id, tools: getCurrentTools(context.messages).map(tool => tool.name).sort() });
|
|
58
|
+
const stream = createAssistantMessageEventStream();
|
|
59
|
+
const message: AssistantMessage = { role: "assistant", api: model.api, provider: model.provider, model: model.id, timestamp: Date.now(), content: [{ type: "text", text: "Offline child" }], stopReason: "stop", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } } };
|
|
60
|
+
queueMicrotask(() => { stream.push({ type: "start", partial: message }); stream.push({ type: "done", reason: "stop", message }); stream.end(); });
|
|
61
|
+
return stream;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const loader = new DefaultResourceLoader({ cwd: root, agentDir, settingsManager, noExtensions: true, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true,
|
|
65
|
+
extensionFactories: [createChildContextExtension({ GENTLE_PI_AGENTS_CHILD: "1", [REQUESTED_TOOLS_ENV]: requested })] });
|
|
66
|
+
await loader.reload();
|
|
67
|
+
const { session } = await createAgentSession({ cwd: root, agentDir, modelRuntime: runtime, model: runtime.getModel("offline-explore", "configured-small")!, resourceLoader: loader, settingsManager, sessionManager: SessionManager.inMemory(), tools: ["read", "grep", "find", "codegraph"] });
|
|
68
|
+
try {
|
|
69
|
+
await session.bindExtensions({ mode: "json" });
|
|
70
|
+
await session.prompt("Map local source without modifying files.");
|
|
71
|
+
assert.deepEqual(observed, [{ model: "configured-small", tools: ["codegraph", "find", "grep", "read"] }]);
|
|
72
|
+
assert.deepEqual(session.getActiveToolNames().sort(), ["codegraph", "find", "grep", "read"]);
|
|
73
|
+
} finally { session.dispose(); rmSync(root, { recursive: true, force: true }); }
|
|
74
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { childContextExtensionPaths } from "../extensions/gentle-agents.ts";
|
|
7
|
+
|
|
8
|
+
// #1690: children of an isolated standalone parent load the forwarded package
|
|
9
|
+
// root, and a parent without the injection signal falls back to the curated
|
|
10
|
+
// child-context/child-safety/nan-provider entries. These tests pin both halves.
|
|
11
|
+
|
|
12
|
+
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
13
|
+
const extensionsDir = join(repoRoot, "extensions");
|
|
14
|
+
const frozenFallback = [join(extensionsDir, "child-context.ts"), join(extensionsDir, "child-safety.ts"), join(extensionsDir, "nan-provider.ts")];
|
|
15
|
+
|
|
16
|
+
function assertFrozenFallback(paths: string[]): void {
|
|
17
|
+
assert.deepEqual(paths, frozenFallback, "the curated child fallback is frozen by #1690; ship new child behavior in the package");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Pi (package-manager.js resolveExtensionEntries/collectAutoExtensionEntries)
|
|
21
|
+
// loads only the entries from a package.json `pi.extensions` or an index.ts/
|
|
22
|
+
// index.js when the directory has one; otherwise it loads every top-level
|
|
23
|
+
// .ts/.js file. Ignore files there could also hide an entry.
|
|
24
|
+
function packageEntrypointProblems(manifest: { pi?: { extensions?: unknown } }, entries: string[]): string[] {
|
|
25
|
+
const problems: string[] = [];
|
|
26
|
+
if (JSON.stringify(manifest.pi?.extensions) !== JSON.stringify(["./extensions"])) problems.push("pi.extensions is not [\"./extensions\"]");
|
|
27
|
+
for (const name of ["index.ts", "index.js", "package.json", ".gitignore", ".ignore", ".fdignore"]) {
|
|
28
|
+
if (entries.includes(name)) problems.push(`extensions/${name} narrows Pi's top-level discovery`);
|
|
29
|
+
}
|
|
30
|
+
for (const name of ["child-context.ts", "child-safety.ts", "nan-provider.ts"]) {
|
|
31
|
+
if (!entries.includes(name)) problems.push(`extensions/${name} is not a top-level entrypoint`);
|
|
32
|
+
}
|
|
33
|
+
return problems;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test("the curated child fallback is frozen by #1690 to child-context, child-safety and nan-provider; new child behavior ships in the package", () => {
|
|
37
|
+
assertFrozenFallback(childContextExtensionPaths());
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("forwarding the package root gives children child-context, child-safety and nan-provider as top-level package entrypoints", () => {
|
|
41
|
+
// This mirrors Pi's discovery rules by hand (package-manager.js
|
|
42
|
+
// resolveExtensionEntries/collectAutoExtensionEntries). The end-to-end proof
|
|
43
|
+
// is the live probe recorded in odd/tasks/fix-1690-standalone-child-package.md.
|
|
44
|
+
const manifest = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
|
|
45
|
+
const entries = readdirSync(extensionsDir);
|
|
46
|
+
assert.deepEqual(packageEntrypointProblems(manifest, entries), []);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Helper self-checks: these exercise the test helpers above, not product code,
|
|
50
|
+
// and do not count as product coverage.
|
|
51
|
+
|
|
52
|
+
test("helper self-check: assertFrozenFallback rejects an extra entry and a reordered list", () => {
|
|
53
|
+
assert.throws(() => assertFrozenFallback([...frozenFallback, join(extensionsDir, "gentle-ai.ts")]), "an extra entry must fail");
|
|
54
|
+
assert.throws(() => assertFrozenFallback([...frozenFallback].reverse()), "the order is part of the contract");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("helper self-check: packageEntrypointProblems flags each rule that narrows Pi's discovery", () => {
|
|
58
|
+
const manifest = { pi: { extensions: ["./extensions"] } };
|
|
59
|
+
const entries = ["child-context.ts", "child-safety.ts", "nan-provider.ts", "gentle-ai.ts"];
|
|
60
|
+
assert.deepEqual(packageEntrypointProblems(manifest, entries), []);
|
|
61
|
+
assert.deepEqual(packageEntrypointProblems(manifest, [...entries, "index.ts"]), ["extensions/index.ts narrows Pi's top-level discovery"]);
|
|
62
|
+
assert.deepEqual(packageEntrypointProblems(manifest, [...entries, "package.json"]), ["extensions/package.json narrows Pi's top-level discovery"]);
|
|
63
|
+
assert.deepEqual(packageEntrypointProblems(manifest, entries.filter((name) => name !== "child-safety.ts")), ["extensions/child-safety.ts is not a top-level entrypoint"]);
|
|
64
|
+
assert.deepEqual(packageEntrypointProblems({ pi: { extensions: ["./extensions/gentle-ai.ts"] } }, entries), ["pi.extensions is not [\"./extensions\"]"]);
|
|
65
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { posix, win32 } from "node:path";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import {
|
|
5
|
+
CHILD_PACKAGE_INJECTION_ENV,
|
|
6
|
+
childPackageExtensionArgs,
|
|
7
|
+
encodeChildPackageInjection,
|
|
8
|
+
parseChildPackageInjection,
|
|
9
|
+
} from "../lib/child-package-injection.ts";
|
|
10
|
+
|
|
11
|
+
const envWith = (value: string | undefined) => ({ [CHILD_PACKAGE_INJECTION_ENV]: value });
|
|
12
|
+
|
|
13
|
+
test("the env name follows the GENTLE_SHELL_* launcher naming", () => {
|
|
14
|
+
assert.equal(CHILD_PACKAGE_INJECTION_ENV, "GENTLE_SHELL_CHILD_PACKAGE_INJECTION");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("encode and parse round-trip both launcher shapes", () => {
|
|
18
|
+
for (const value of [
|
|
19
|
+
{ noExtensions: false, extensionPaths: ["/pkg"] },
|
|
20
|
+
{ noExtensions: true, extensionPaths: ["/agent/npm/node_modules/other", "/agent/extensions/a.ts", "/pkg"] },
|
|
21
|
+
]) {
|
|
22
|
+
const encoded = encodeChildPackageInjection(value);
|
|
23
|
+
assert.deepEqual(JSON.parse(encoded), { version: 1, ...value });
|
|
24
|
+
assert.deepEqual(parseChildPackageInjection(envWith(encoded)), value);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("parse returns undefined when the value is absent or empty", () => {
|
|
29
|
+
assert.equal(parseChildPackageInjection({}), undefined);
|
|
30
|
+
assert.equal(parseChildPackageInjection(envWith(undefined)), undefined);
|
|
31
|
+
assert.equal(parseChildPackageInjection(envWith("")), undefined);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("parse rejects malformed values without throwing", () => {
|
|
35
|
+
const rejected = [
|
|
36
|
+
"{not json",
|
|
37
|
+
"null",
|
|
38
|
+
"[]",
|
|
39
|
+
"42",
|
|
40
|
+
JSON.stringify({ noExtensions: false, extensionPaths: ["/pkg"] }),
|
|
41
|
+
JSON.stringify({ version: 2, noExtensions: false, extensionPaths: ["/pkg"] }),
|
|
42
|
+
JSON.stringify({ version: "1", noExtensions: false, extensionPaths: ["/pkg"] }),
|
|
43
|
+
JSON.stringify({ version: 1, extensionPaths: ["/pkg"] }),
|
|
44
|
+
JSON.stringify({ version: 1, noExtensions: "false", extensionPaths: ["/pkg"] }),
|
|
45
|
+
JSON.stringify({ version: 1, noExtensions: false }),
|
|
46
|
+
JSON.stringify({ version: 1, noExtensions: false, extensionPaths: "/pkg" }),
|
|
47
|
+
JSON.stringify({ version: 1, noExtensions: false, extensionPaths: [] }),
|
|
48
|
+
JSON.stringify({ version: 1, noExtensions: false, extensionPaths: [42] }),
|
|
49
|
+
JSON.stringify({ version: 1, noExtensions: false, extensionPaths: [null] }),
|
|
50
|
+
JSON.stringify({ version: 1, noExtensions: false, extensionPaths: [""] }),
|
|
51
|
+
JSON.stringify({ version: 1, noExtensions: false, extensionPaths: ["pkg"] }),
|
|
52
|
+
JSON.stringify({ version: 1, noExtensions: true, extensionPaths: ["/pkg", "./extensions/a.ts"] }),
|
|
53
|
+
];
|
|
54
|
+
for (const value of rejected) {
|
|
55
|
+
assert.doesNotThrow(() => parseChildPackageInjection(envWith(value)), value);
|
|
56
|
+
assert.equal(parseChildPackageInjection(envWith(value)), undefined, value);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("parse checks absoluteness with the injected path flavor, so Windows paths are accepted", () => {
|
|
61
|
+
const windows = encodeChildPackageInjection({ noExtensions: false, extensionPaths: ["C:\\x\\gentle-pi"] });
|
|
62
|
+
assert.deepEqual(parseChildPackageInjection(envWith(windows), win32), { noExtensions: false, extensionPaths: ["C:\\x\\gentle-pi"] });
|
|
63
|
+
assert.equal(parseChildPackageInjection(envWith(windows), posix), undefined);
|
|
64
|
+
const driveRelative = encodeChildPackageInjection({ noExtensions: false, extensionPaths: ["C:x"] });
|
|
65
|
+
assert.equal(parseChildPackageInjection(envWith(driveRelative), win32), undefined);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("childPackageExtensionArgs emits plain argv elements, with --no-extensions first for a takeover", () => {
|
|
69
|
+
assert.deepEqual(childPackageExtensionArgs({ noExtensions: false, extensionPaths: ["/pkg"] }), ["--extension", "/pkg"]);
|
|
70
|
+
assert.deepEqual(
|
|
71
|
+
childPackageExtensionArgs({ noExtensions: true, extensionPaths: ["/other dir/pkg", "C:\\x y\\a.ts", "/pkg"] }),
|
|
72
|
+
["--no-extensions", "--extension", "/other dir/pkg", "--extension", "C:\\x y\\a.ts", "--extension", "/pkg"],
|
|
73
|
+
);
|
|
74
|
+
});
|
|
@@ -80,3 +80,15 @@ test("primary requires fresh confirmation, cancellation and missing UI block", a
|
|
|
80
80
|
const blockers = h.events.filter((entry) => entry.name === "herdr:blocked").map((entry) => (entry.data as { active: boolean }).active);
|
|
81
81
|
assert.deepEqual(blockers, [true, false, true, false, true, false, true, false]);
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
test("children block recognized destructive commands sent to bash_background too", async () => {
|
|
85
|
+
const h = harness(true);
|
|
86
|
+
const result = await h.handlers[0]({ toolName: "bash_background", input: { command: "echo ok && rm -rf ./data" } }, context(async () => { throw new Error("child must never prompt"); }));
|
|
87
|
+
assert.equal((result as { block?: boolean })?.block, true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("children block recognized destructive commands sent to monitor too", async () => {
|
|
91
|
+
const h = harness(true);
|
|
92
|
+
const result = await h.handlers[0]({ toolName: "monitor", input: { command: "echo ok && rm -rf ./data" } }, context(async () => { throw new Error("child must never prompt"); }));
|
|
93
|
+
assert.equal((result as { block?: boolean })?.block, true);
|
|
94
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync, realpathSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { childArguments, type TaskRequest } from "../lib/agents-runner.ts";
|
|
7
|
+
import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { createAssistantMessageEventStream, getCurrentTools, type AssistantMessage } from "@earendil-works/pi-ai";
|
|
9
|
+
|
|
10
|
+
const webTools = ["web_enable", "web_search", "source_check", "fetch_content", "get_search_content"];
|
|
11
|
+
|
|
12
|
+
function fixture() {
|
|
13
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "child-web-package-")));
|
|
14
|
+
const agentDir = join(root, "agent");
|
|
15
|
+
const packageRoot = join(agentDir, "npm", "node_modules", "pi-web-access");
|
|
16
|
+
mkdirSync(join(packageRoot, "dist"), { recursive: true });
|
|
17
|
+
writeFileSync(join(packageRoot, "package.json"), JSON.stringify({ name: "pi-web-access", type: "module", pi: { extensions: ["./dist"] } }));
|
|
18
|
+
writeFileSync(join(packageRoot, "dist", "index.js"), "export default function(api) {}\n");
|
|
19
|
+
const request: TaskRequest = {
|
|
20
|
+
agent: {
|
|
21
|
+
name: "gentle-ai-explore", description: "Public fixture", filePath: join(root, "explorer.md"), scope: "global",
|
|
22
|
+
tools: ["read", "grep", "find", "codegraph", ...webTools], instructions: "Explore without modifying source.",
|
|
23
|
+
model: undefined, thinking: undefined, mode: undefined,
|
|
24
|
+
},
|
|
25
|
+
prompt: "Explore the public fixture", label: undefined, context: undefined, mode: "task",
|
|
26
|
+
parentSessionId: "fixture-parent", resumeSessionPath: undefined,
|
|
27
|
+
cwd: root, sessionDir: join(root, "sessions"), noExtensions: true,
|
|
28
|
+
extensionPaths: [join(root, "child-context.ts"), join(root, "child-safety.ts"), join(root, "nan-provider.ts")],
|
|
29
|
+
env: { PI_CODING_AGENT_DIR: agentDir }, model: { provider: "fixture", id: "configured-small" }, thinking: "low",
|
|
30
|
+
};
|
|
31
|
+
for (const path of request.extensionPaths!) writeFileSync(path, "export default function(api) {}\n");
|
|
32
|
+
return { root, packageRoot, request };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function paths(args: string[]): string[] {
|
|
36
|
+
return args.flatMap((arg, index) => arg === "-e" || arg === "--extension" ? [args[index + 1]] : []);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test("requested direct web loads only the installed package alongside the frozen owned bootstrap", () => {
|
|
40
|
+
const f = fixture();
|
|
41
|
+
try {
|
|
42
|
+
f.request.env.HOME = f.request.env.USERPROFILE = join(f.root, "other-home");
|
|
43
|
+
const args = childArguments(f.request);
|
|
44
|
+
assert.deepEqual(paths(args), [...f.request.extensionPaths!, f.packageRoot]);
|
|
45
|
+
assert.ok(args.includes("--no-extensions"), "do not enable ambient plugins to obtain web access");
|
|
46
|
+
assert.equal(args[args.indexOf("--model") + 1], "fixture/configured-small:low");
|
|
47
|
+
assert.equal(args[args.indexOf("--tools") + 1], [...f.request.agent.tools, "subagent_parent_message"].join(","));
|
|
48
|
+
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("partial or absent web scope never loads a package or broadens the requested tools", () => {
|
|
52
|
+
const f = fixture();
|
|
53
|
+
try {
|
|
54
|
+
for (const tools of [["read", "grep", "find"], ["web_enable"], ["fetch_content"], webTools.slice(1)]) {
|
|
55
|
+
const request = { ...f.request, agent: { ...f.request.agent, tools } };
|
|
56
|
+
assert.deepEqual(paths(childArguments(request)), request.extensionPaths);
|
|
57
|
+
}
|
|
58
|
+
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("already injected web package is not added twice", () => {
|
|
62
|
+
const f = fixture();
|
|
63
|
+
try {
|
|
64
|
+
const request = { ...f.request, extensionPaths: [...f.request.extensionPaths!, f.packageRoot] };
|
|
65
|
+
assert.deepEqual(paths(childArguments(request)), request.extensionPaths);
|
|
66
|
+
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("a symlink to the injected web package is not loaded again", () => {
|
|
70
|
+
const f = fixture();
|
|
71
|
+
try {
|
|
72
|
+
const alias = join(f.root, "web-alias");
|
|
73
|
+
symlinkSync(f.packageRoot, alias, "junction");
|
|
74
|
+
for (const path of [alias, "web-alias"]) {
|
|
75
|
+
const request = { ...f.request, extensionPaths: [...f.request.extensionPaths!, path] };
|
|
76
|
+
assert.deepEqual(paths(childArguments(request)), request.extensionPaths);
|
|
77
|
+
}
|
|
78
|
+
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("web lookup uses the child's home instead of the parent agent directory", () => {
|
|
82
|
+
const f = fixture();
|
|
83
|
+
const parentAgentDir = process.env.PI_CODING_AGENT_DIR;
|
|
84
|
+
try {
|
|
85
|
+
process.env.PI_CODING_AGENT_DIR = join(f.root, "parent-agent");
|
|
86
|
+
const home = join(f.root, "child-home");
|
|
87
|
+
const packageRoot = join(home, ".pi", "agent", "npm", "node_modules", "pi-web-access");
|
|
88
|
+
mkdirSync(packageRoot, { recursive: true });
|
|
89
|
+
const homeEnv = process.platform === "win32" ? { USERPROFILE: home } : { HOME: home };
|
|
90
|
+
for (const override of [undefined, "", "~/.pi/agent"]) {
|
|
91
|
+
const request = { ...f.request, env: { ...homeEnv, PI_CODING_AGENT_DIR: override } };
|
|
92
|
+
assert.deepEqual(paths(childArguments(request)), [...f.request.extensionPaths!, packageRoot]);
|
|
93
|
+
}
|
|
94
|
+
} finally {
|
|
95
|
+
if (parentAgentDir === undefined) delete process.env.PI_CODING_AGENT_DIR;
|
|
96
|
+
else process.env.PI_CODING_AGENT_DIR = parentAgentDir;
|
|
97
|
+
rmSync(f.root, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("missing or incompatible SDK package lookup never installs or enables ambient extensions", () => {
|
|
102
|
+
const f = fixture();
|
|
103
|
+
const original = DefaultPackageManager.prototype.getInstalledPath;
|
|
104
|
+
try {
|
|
105
|
+
for (const lookup of [() => undefined, () => { throw new Error("unsupported package lookup"); }]) {
|
|
106
|
+
DefaultPackageManager.prototype.getInstalledPath = lookup;
|
|
107
|
+
const args = childArguments(f.request);
|
|
108
|
+
assert.deepEqual(paths(args), f.request.extensionPaths);
|
|
109
|
+
assert.ok(args.includes("--no-extensions"));
|
|
110
|
+
}
|
|
111
|
+
} finally {
|
|
112
|
+
DefaultPackageManager.prototype.getInstalledPath = original;
|
|
113
|
+
rmSync(f.root, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("explicit capability scope, not agent name, determines optional web loading", () => {
|
|
118
|
+
const f = fixture();
|
|
119
|
+
try {
|
|
120
|
+
for (const name of ["renamed-explorer", "gentle-ai-worker", "gentle-ai-verify"]) {
|
|
121
|
+
const optedIn = { ...f.request, agent: { ...f.request.agent, name } };
|
|
122
|
+
assert.deepEqual(paths(childArguments(optedIn)), [...f.request.extensionPaths!, f.packageRoot]);
|
|
123
|
+
const localOnly = { ...optedIn, agent: { ...optedIn.agent, tools: ["read", "grep", "find"] } };
|
|
124
|
+
assert.deepEqual(paths(childArguments(localOnly)), f.request.extensionPaths);
|
|
125
|
+
}
|
|
126
|
+
} finally { rmSync(f.root, { recursive: true, force: true }); }
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
async function sdkFixture(block: boolean, injectAlias = false) {
|
|
130
|
+
const f = fixture();
|
|
131
|
+
const marker = "controlled public web evidence";
|
|
132
|
+
const counter = `webFixture_${Math.random().toString(36).slice(2)}`;
|
|
133
|
+
const state = globalThis as unknown as Record<string, number>;
|
|
134
|
+
state[counter] = 0;
|
|
135
|
+
const hooks = `${counter}_hooks`;
|
|
136
|
+
state[hooks] = 0;
|
|
137
|
+
if (injectAlias) {
|
|
138
|
+
const alias = join(f.root, "web-alias");
|
|
139
|
+
symlinkSync(f.packageRoot, alias, "junction");
|
|
140
|
+
f.request.extensionPaths!.push(alias);
|
|
141
|
+
}
|
|
142
|
+
writeFileSync(join(f.packageRoot, "dist", "index.js"), `
|
|
143
|
+
export default function(api) {
|
|
144
|
+
api.on("session_start", () => { globalThis[${JSON.stringify(hooks)}]++; });
|
|
145
|
+
for (const name of ${JSON.stringify(webTools)}) api.registerTool({
|
|
146
|
+
name, label: name, description: "Controlled web fixture",
|
|
147
|
+
parameters: { type: "object", properties: { url: { type: "string" } }, additionalProperties: false },
|
|
148
|
+
async execute() {
|
|
149
|
+
globalThis[${JSON.stringify(counter)}]++;
|
|
150
|
+
return { content: [{ type: "text", text: ${JSON.stringify(marker)} }] };
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
`);
|
|
155
|
+
const settingsManager = SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false } });
|
|
156
|
+
const runtime = await ModelRuntime.create({ authPath: join(f.root, "offline-auth.json"), modelsPath: null, refreshOnCreate: false });
|
|
157
|
+
const observed: { model: string; tools: string[] }[] = [];
|
|
158
|
+
let turn = 0;
|
|
159
|
+
runtime.registerProvider("offline-web", {
|
|
160
|
+
api: "offline-web-test", apiKey: "offline-dummy", baseUrl: "http://offline.invalid",
|
|
161
|
+
models: [{ id: "configured-small", name: "Configured small fixture", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 100000, maxTokens: 1000 }],
|
|
162
|
+
streamSimple(model, context) {
|
|
163
|
+
observed.push({ model: model.id, tools: getCurrentTools(context.messages).map(tool => tool.name).sort() });
|
|
164
|
+
const calling = turn++ === 0;
|
|
165
|
+
const message: AssistantMessage = {
|
|
166
|
+
role: "assistant", api: model.api, provider: model.provider, model: model.id, timestamp: Date.now(),
|
|
167
|
+
content: calling ? [{ type: "toolCall", id: "fixture-fetch", name: "fetch_content", arguments: { url: "https://public.fixture/evidence" } }] : [{ type: "text", text: "Finished" }],
|
|
168
|
+
stopReason: calling ? "toolUse" : "stop",
|
|
169
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
|
170
|
+
};
|
|
171
|
+
const stream = createAssistantMessageEventStream();
|
|
172
|
+
queueMicrotask(() => { stream.push({ type: "start", partial: message }); stream.push({ type: "done", reason: message.stopReason as "stop" | "toolUse", message }); stream.end(); });
|
|
173
|
+
return stream;
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
const calls: string[] = [];
|
|
177
|
+
const loader = new DefaultResourceLoader({
|
|
178
|
+
cwd: f.root, agentDir: join(f.root, "agent"), settingsManager,
|
|
179
|
+
noExtensions: true, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true,
|
|
180
|
+
additionalExtensionPaths: paths(childArguments(f.request)),
|
|
181
|
+
extensionFactories: [api => {
|
|
182
|
+
api.on("session_start", () => { api.setActiveTools(webTools); });
|
|
183
|
+
api.on("tool_call", event => {
|
|
184
|
+
calls.push(event.toolName);
|
|
185
|
+
if (block) return { block: true, reason: "fixture permission denied" };
|
|
186
|
+
});
|
|
187
|
+
}],
|
|
188
|
+
});
|
|
189
|
+
let session: Awaited<ReturnType<typeof createAgentSession>>["session"] | undefined;
|
|
190
|
+
try {
|
|
191
|
+
await loader.reload();
|
|
192
|
+
assert.deepEqual(loader.getExtensions().errors, []);
|
|
193
|
+
({ session } = await createAgentSession({ cwd: f.root, agentDir: join(f.root, "agent"), modelRuntime: runtime, model: runtime.getModel("offline-web", "configured-small")!, resourceLoader: loader, settingsManager, sessionManager: SessionManager.inMemory(), tools: webTools }));
|
|
194
|
+
await session.bindExtensions({ mode: "json" });
|
|
195
|
+
assert.equal(state[hooks], 1, "the package's session hook must register only once");
|
|
196
|
+
await session.prompt("Retrieve the controlled public source directly.");
|
|
197
|
+
assert.deepEqual(calls, ["fetch_content"], JSON.stringify({ observed, messages: session.messages.map(message => ({ role: message.role, errorMessage: message.role === "assistant" ? message.errorMessage : undefined, content: message.role === "toolResult" ? message.content : undefined })) }));
|
|
198
|
+
assert.ok(observed.length >= 2);
|
|
199
|
+
assert.ok(observed.every(request => request.model === "configured-small"));
|
|
200
|
+
assert.ok(observed[0].tools.includes("fetch_content"), "registration must reach an actual SDK model request");
|
|
201
|
+
assert.equal(state[counter], block ? 0 : 1, "permission interception occurs before backend execution");
|
|
202
|
+
const result = session.messages.find(message => message.role === "toolResult");
|
|
203
|
+
assert.ok(result?.role === "toolResult");
|
|
204
|
+
assert.equal(Boolean(result.isError), block);
|
|
205
|
+
assert.ok(JSON.stringify(result.content).includes(block ? "fixture permission denied" : marker));
|
|
206
|
+
} finally {
|
|
207
|
+
session?.dispose();
|
|
208
|
+
delete state[counter];
|
|
209
|
+
delete state[hooks];
|
|
210
|
+
rmSync(f.root, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
test("production child paths register and execute web through actual SDK hooks without network", async () => {
|
|
215
|
+
await sdkFixture(false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("a symlink-injected web package registers actual SDK hooks once", async () => {
|
|
219
|
+
await sdkFixture(false, true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("normal SDK permission hook blocks web execution before the backend", async () => {
|
|
223
|
+
await sdkFixture(true);
|
|
224
|
+
});
|
|
@@ -39,6 +39,41 @@ test("CodeGraph tool rejects non-project, nested-project, HOME, and temporary wo
|
|
|
39
39
|
assert.equal(calls, 0);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
test("CodeGraph query timeout is reported distinctly from user abort and never bounds init (#1901)", async (t) => {
|
|
43
|
+
const cwd = workspace(t);
|
|
44
|
+
const ctx = { cwd } as ExtensionContext;
|
|
45
|
+
// Behaves like execFile: settles only when its signal aborts, then rejects with AbortError.
|
|
46
|
+
const hanging: CodeGraphRunner = (_args, options) => new Promise((_resolve, reject) => {
|
|
47
|
+
options.signal?.addEventListener("abort", () => reject(Object.assign(new Error("The operation was aborted"), { name: "AbortError", code: "ABORT_ERR" })), { once: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const bounded = createCodeGraphTool(hanging, { queryTimeoutMs: 20 });
|
|
51
|
+
for (const parameters of [{ operation: "query", query: "x" }, { operation: "explore", query: "x" }] as const) {
|
|
52
|
+
const result = await bounded.execute("test", parameters, undefined, undefined, ctx);
|
|
53
|
+
assert.match(result.content[0].text, /CodeGraph timed out after 20 ms/);
|
|
54
|
+
assert.match(result.content[0].text, /Use read, grep, and find/);
|
|
55
|
+
assert.equal((result.details as { status: string }).status, "failed");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// A user cancellation is not a timeout.
|
|
59
|
+
const user = new AbortController();
|
|
60
|
+
setTimeout(() => user.abort(), 5);
|
|
61
|
+
const cancelled = await createCodeGraphTool(hanging, { queryTimeoutMs: 60_000 }).execute("test", { operation: "query", query: "x" }, user.signal, undefined, ctx);
|
|
62
|
+
assert.doesNotMatch(cancelled.content[0].text, /timed out/);
|
|
63
|
+
assert.match(cancelled.content[0].text, /CodeGraph failed to run/);
|
|
64
|
+
|
|
65
|
+
// init is the legitimately slow first index: only the user's own signal bounds it.
|
|
66
|
+
let initSignal: AbortSignal | undefined = new AbortController().signal;
|
|
67
|
+
const slowInit = createCodeGraphTool(async (_args, options) => {
|
|
68
|
+
initSignal = options.signal;
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
70
|
+
return { stdout: "indexed", stderr: "" };
|
|
71
|
+
}, { queryTimeoutMs: 20 });
|
|
72
|
+
const init = await slowInit.execute("test", { operation: "init" }, undefined, undefined, ctx);
|
|
73
|
+
assert.deepEqual(init.content, [{ type: "text", text: "indexed" }]);
|
|
74
|
+
assert.equal(initSignal, undefined);
|
|
75
|
+
});
|
|
76
|
+
|
|
42
77
|
test("CodeGraph tool runs only fixed cwd-scoped init, query, and explore commands", async (t) => {
|
|
43
78
|
const cwd = workspace(t);
|
|
44
79
|
const calls: Array<{ args: readonly string[]; cwd: string }> = [];
|
|
@@ -42,12 +42,13 @@ function registeredCodemode() {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function context(overrides: Partial<ToolRenderContext> = {}): ToolRenderContext {
|
|
45
|
-
|
|
45
|
+
const fixture = {
|
|
46
46
|
args: { code: "await tools.read({path: '/private/argument'});" },
|
|
47
47
|
toolCallId: "code-1", invalidate() {}, lastComponent: undefined, state: {}, cwd: "/fixture",
|
|
48
48
|
executionStarted: true, argsComplete: true, isPartial: false, expanded: false, showImages: false,
|
|
49
|
-
isError: false, ...overrides,
|
|
49
|
+
isError: false, durationMs: undefined, outputPad: 0, ...overrides,
|
|
50
50
|
};
|
|
51
|
+
return fixture;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
function result(calls: unknown = [], text = "Script completed\nWall time 0.1 seconds\nOutput:\n"): AgentToolResult<unknown> {
|
|
@@ -108,6 +109,7 @@ test("public loadout modes preserve hidden and deferred exposure policy", async
|
|
|
108
109
|
declared: [tool, direct], callable: [direct, deferred], registered: [tool, direct, deferred, hidden],
|
|
109
110
|
getExposure: (name: string) => name === "deferred_fixture" ? "deferred" : name === "hidden_fixture" ? "hidden" : "direct",
|
|
110
111
|
getNamespace: () => undefined,
|
|
112
|
+
getPromptGuidelines: () => [],
|
|
111
113
|
} as unknown as Parameters<NonNullable<ToolDefinition["prepareLoadout"]>>[0];
|
|
112
114
|
const changes = tool.prepareLoadout!(loadout)!;
|
|
113
115
|
assert.deepEqual(changes.hiddenDeclarations, mode === "only" ? ["direct_fixture"] : []);
|
|
@@ -429,6 +431,109 @@ function piRow(tool: ToolDefinition, value: AgentToolResult<unknown> | undefined
|
|
|
429
431
|
|
|
430
432
|
const pending = () => context({ isPartial: true, executionStarted: true, state: {} });
|
|
431
433
|
|
|
434
|
+
test("unchanged Code frames reuse paint work in both styles, widths and views", (t) => {
|
|
435
|
+
const original = cardStyle();
|
|
436
|
+
t.after(() => setCardStyle(original));
|
|
437
|
+
const tool = decorateCodemodeTool({ name: "codemode" } as ToolDefinition);
|
|
438
|
+
const output = Array.from({ length: 300 }, (_, i) => `${i}: 界🌹 e\u0301 \x1b[31mcolored\x1b[0m ${"words ".repeat(12)}`).join("\n");
|
|
439
|
+
for (const style of Object.values(CARD_STYLE)) {
|
|
440
|
+
setCardStyle(style);
|
|
441
|
+
for (const expanded of [false, true]) {
|
|
442
|
+
for (const width of [80, 160]) {
|
|
443
|
+
let paints = 0;
|
|
444
|
+
const painted = {
|
|
445
|
+
fg: (role: string, text: string) => { paints++; return floatTheme.fg(role, text); },
|
|
446
|
+
bg: (role: string, text: string) => { paints++; return floatTheme.bg(role, text); },
|
|
447
|
+
};
|
|
448
|
+
const ctx = context({ expanded });
|
|
449
|
+
const components = [tool.renderCall!(ctx.args, painted as never, ctx),
|
|
450
|
+
tool.renderResult!(result([], output), { expanded, isPartial: false }, painted as never, ctx)];
|
|
451
|
+
for (const component of components) {
|
|
452
|
+
const first = [...component.render(width)];
|
|
453
|
+
const afterFirst = paints;
|
|
454
|
+
for (let repeat = 0; repeat < 20; repeat++) assert.deepEqual(component.render(width + 0.5), first);
|
|
455
|
+
assert.equal(paints, afterFirst, "unchanged frame must not repaint");
|
|
456
|
+
component.invalidate();
|
|
457
|
+
assert.deepEqual(component.render(width), first, "invalidation preserves exact ANSI output");
|
|
458
|
+
assert.ok(paints > afterFirst);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test("Code cache replaces its frame on width, style and theme invalidation", (t) => {
|
|
466
|
+
const original = cardStyle();
|
|
467
|
+
t.after(() => setCardStyle(original));
|
|
468
|
+
setCardStyle(CARD_STYLE.NEON);
|
|
469
|
+
const tool = decorateCodemodeTool({ name: "codemode" } as ToolDefinition);
|
|
470
|
+
let paints = 0;
|
|
471
|
+
let color = "\x1b[31m";
|
|
472
|
+
const painted = {
|
|
473
|
+
fg: (_role: string, text: string) => { paints++; return `${color}${text}\x1b[0m`; },
|
|
474
|
+
bg: floatTheme.bg,
|
|
475
|
+
};
|
|
476
|
+
const ctx = context();
|
|
477
|
+
const value = result([], "hello 界 e\u0301\nsecond");
|
|
478
|
+
const fresh = () => tool.renderResult!(value, { expanded: false, isPartial: false }, painted as never, ctx);
|
|
479
|
+
const component = fresh();
|
|
480
|
+
const first = [...component.render(80)];
|
|
481
|
+
const before = paints;
|
|
482
|
+
assert.deepEqual(component.render(80.9), first);
|
|
483
|
+
assert.equal(paints, before);
|
|
484
|
+
assert.deepEqual(component.render(160), fresh().render(160));
|
|
485
|
+
assert.ok(paints > before);
|
|
486
|
+
setCardStyle(CARD_STYLE.FLOAT);
|
|
487
|
+
assert.deepEqual(component.render(160), fresh().render(160));
|
|
488
|
+
setCardStyle(CARD_STYLE.NEON);
|
|
489
|
+
assert.deepEqual(component.render(80), first);
|
|
490
|
+
color = "\x1b[35m";
|
|
491
|
+
component.invalidate();
|
|
492
|
+
const changed = component.render(80);
|
|
493
|
+
assert.deepEqual(changed, fresh().render(80));
|
|
494
|
+
assert.notDeepEqual(changed, first);
|
|
495
|
+
assert.deepEqual(changed.map(stripAnsi), first.map(stripAnsi));
|
|
496
|
+
assert.deepEqual(component.render(0), []);
|
|
497
|
+
assert.deepEqual(component.render(-1), []);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("cached running Code calls notice result arrival and completion", (t) => {
|
|
501
|
+
const original = cardStyle();
|
|
502
|
+
t.after(() => setCardStyle(original));
|
|
503
|
+
const tool = decorateCodemodeTool({ name: "codemode" } as ToolDefinition);
|
|
504
|
+
for (const style of Object.values(CARD_STYLE)) {
|
|
505
|
+
setCardStyle(style);
|
|
506
|
+
for (const transition of ["result", "completion"]) {
|
|
507
|
+
const ctx = pending();
|
|
508
|
+
const component = tool.renderCall!(ctx.args, floatTheme as never, ctx);
|
|
509
|
+
const running = [...component.render(80)];
|
|
510
|
+
assert.match(running.map(stripAnsi).join("\n"), /running…/);
|
|
511
|
+
assert.deepEqual(component.render(80), running);
|
|
512
|
+
if (transition === "result") tool.renderResult!(result([], "partial"), { expanded: false, isPartial: true }, floatTheme as never, ctx);
|
|
513
|
+
else ctx.isPartial = false;
|
|
514
|
+
const changed = component.render(80);
|
|
515
|
+
assert.doesNotMatch(changed.map(stripAnsi).join("\n"), /running…/);
|
|
516
|
+
assert.deepEqual(changed, tool.renderCall!(ctx.args, floatTheme as never, ctx).render(80));
|
|
517
|
+
assert.deepEqual(component.render(80), changed);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test("cached Code results do not leak content across partial, final or expanded components", () => {
|
|
523
|
+
const tool = decorateCodemodeTool({ name: "codemode" } as ToolDefinition);
|
|
524
|
+
const ctx = pending();
|
|
525
|
+
const partial = tool.renderResult!(result([], "partial payload"), { expanded: false, isPartial: true }, theme as never, ctx);
|
|
526
|
+
const first = [...partial.render(80)];
|
|
527
|
+
assert.deepEqual(partial.render(80), first);
|
|
528
|
+
const value = result([], "final payload\nsecond\nthird\nfourth detail");
|
|
529
|
+
const final = tool.renderResult!(value, { expanded: false, isPartial: false }, theme as never, ctx);
|
|
530
|
+
const expanded = tool.renderResult!(value, { expanded: true, isPartial: false }, theme as never, context({ expanded: true }));
|
|
531
|
+
assert.match(final.render(80).join("\n"), /final payload/);
|
|
532
|
+
assert.doesNotMatch(final.render(80).join("\n"), /partial payload|fourth detail/);
|
|
533
|
+
assert.match(expanded.render(80).join("\n"), /fourth detail/);
|
|
534
|
+
assert.deepEqual(partial.render(80), first);
|
|
535
|
+
});
|
|
536
|
+
|
|
432
537
|
test("a running Code card closes its own frame with one running row until a result exists", () => {
|
|
433
538
|
const tool = registeredCodemode();
|
|
434
539
|
const roles: string[] = [];
|