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
package/lib/agents-runner.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { isSessionChangeEvidence, type SessionChangeEvidence } from "./session-changes.ts";
|
|
2
|
-
import
|
|
2
|
+
import { chmodSync, existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { DefaultPackageManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { tmpdir, userInfo } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { Duplex, type Readable, type Writable } from "node:stream";
|
|
3
7
|
import { stripVTControlCharacters } from "node:util";
|
|
4
|
-
import { RESEARCH_SELECTION_ENV } from "./sdd-research-capabilities.ts";
|
|
5
8
|
import { withoutInteractiveHost } from "./rpc-host.ts";
|
|
9
|
+
import { childPackageExtensionArgs } from "./child-package-injection.ts";
|
|
6
10
|
import { AGENT_MODE, formatModelRef, type AgentDefinition, type AgentMode, type ModelRef } from "./agents-config.ts";
|
|
7
11
|
import { CHILD_QUERY_MAX_INFLIGHT, CHILD_QUERY_TIMEOUT_MS, parseChildFrame, validChildMessage, validChildQueryId } from "./agents-messaging.ts";
|
|
8
12
|
import { ParentStandingReviewPermissionBroker } from "./review-session-standing-permission-ipc.ts";
|
|
9
|
-
import { isFinished, normalizeRpcEvent, TASK_EVENT, TASK_STATUS, taskLabel, type AskRequest, type ChildResponseObservation, type TaskRecord, type TaskStore } from "./agents-protocol.ts";
|
|
13
|
+
import { isFinished, normalizeRpcEvent, ToolArgumentProgress, TASK_EVENT, TASK_STATUS, taskLabel, type AskRequest, type ChildResponseObservation, type TaskRecord, type TaskStore } from "./agents-protocol.ts";
|
|
14
|
+
import { WriterSurfaceRegistry, writerSurfaceConflictMessage } from "./writer-surfaces.ts";
|
|
10
15
|
|
|
11
16
|
// Gentle Agents runner. Every subagent is its own `pi --mode rpc` process:
|
|
12
17
|
// the host never runs subagent work on the TUI thread. It writes JSON
|
|
@@ -14,12 +19,12 @@ import { isFinished, normalizeRpcEvent, TASK_EVENT, TASK_STATUS, taskLabel, type
|
|
|
14
19
|
// and enforces an inactivity watchdog per task.
|
|
15
20
|
|
|
16
21
|
export interface ChildLike {
|
|
17
|
-
pid
|
|
22
|
+
pid?: number;
|
|
18
23
|
connected?: boolean;
|
|
19
|
-
stdin: Writable;
|
|
20
|
-
stdout: Readable;
|
|
24
|
+
stdin: Writable | null;
|
|
25
|
+
stdout: Readable | null;
|
|
21
26
|
stderr: Readable | null | undefined;
|
|
22
|
-
stdio?: Array<
|
|
27
|
+
stdio?: Array<Readable | Writable | null | undefined>;
|
|
23
28
|
kill(signal?: NodeJS.Signals): boolean;
|
|
24
29
|
send?(message: Record<string, unknown>, callback?: (error: Error | null) => void): boolean;
|
|
25
30
|
disconnect?(): void;
|
|
@@ -33,6 +38,7 @@ export interface SpawnOptions {
|
|
|
33
38
|
cwd: string;
|
|
34
39
|
env: NodeJS.ProcessEnv;
|
|
35
40
|
detached?: boolean;
|
|
41
|
+
windowsHide?: boolean;
|
|
36
42
|
stdio?: Array<"pipe" | "ignore" | "inherit" | "ipc" | "overlapped">;
|
|
37
43
|
}
|
|
38
44
|
|
|
@@ -53,6 +59,7 @@ export interface RunnerDeps {
|
|
|
53
59
|
now(): number;
|
|
54
60
|
schedule(fn: () => void, ms: number): () => void;
|
|
55
61
|
pi: PiCommand;
|
|
62
|
+
resolvePi?(): PiCommand;
|
|
56
63
|
process?: ProcessControl;
|
|
57
64
|
}
|
|
58
65
|
|
|
@@ -73,6 +80,7 @@ export interface AskAnswer {
|
|
|
73
80
|
export interface TaskQuery {
|
|
74
81
|
taskId: string;
|
|
75
82
|
requestId: string;
|
|
83
|
+
message: string;
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
export const MAX_CHILD_RESPONSE_OBSERVATIONS = 128;
|
|
@@ -104,51 +112,12 @@ export interface RunnerHooks {
|
|
|
104
112
|
// Accepts a child notification only while the originating parent session is active.
|
|
105
113
|
onNotification?(task: TaskRecord, message: string): boolean | void;
|
|
106
114
|
onQuery?(task: TaskRecord, requestId: string, message: string): boolean | void;
|
|
115
|
+
onQuerySettled?(taskId: string, requestId: string, outcome: "replied" | "expired"): void;
|
|
107
116
|
// Parent-only observation of a paired successful filesystem tool, not prose.
|
|
108
117
|
onSuccessfulMutation?(task: TaskRecord, tool: { toolName: "write" | "edit"; toolCallId: string; path: string; evidence?: SessionChangeEvidence }): void | Promise<void>;
|
|
109
118
|
}
|
|
110
119
|
|
|
111
|
-
export interface RemediationHarnessPlan { command?: string; naReason?: string }
|
|
112
|
-
export interface RemediationRollbackPlan { boundary: string; command: string }
|
|
113
|
-
export interface RemediationScope { cwd: string; editPaths: string[]; commands: string[]; allowedEditRoots: string[] }
|
|
114
|
-
export interface RemediationPlan {
|
|
115
|
-
editPaths?: string[];
|
|
116
|
-
cwd: string;
|
|
117
|
-
commands: string[];
|
|
118
|
-
runtimeHarness: RemediationHarnessPlan;
|
|
119
|
-
rollback: RemediationRollbackPlan;
|
|
120
|
-
}
|
|
121
|
-
const concrete = (value: unknown): value is string => typeof value === "string" && value.trim() === value && value.length > 3 && value.length <= 4096 && !/[\0\r\n]/.test(value);
|
|
122
|
-
export function parseRemediationPlan(value: unknown, cwd: string): RemediationPlan {
|
|
123
|
-
const plan = value as RemediationPlan;
|
|
124
|
-
if (!plan || plan.cwd !== cwd || !Array.isArray(plan.commands) || plan.commands.length < 1 || plan.commands.length > 16 || !plan.commands.every(concrete) ||
|
|
125
|
-
!concrete(plan.rollback?.boundary) || !concrete(plan.rollback?.command) || !plan.runtimeHarness ||
|
|
126
|
-
!(concrete(plan.runtimeHarness.command) && plan.runtimeHarness.naReason === undefined || plan.runtimeHarness.command === undefined && concrete(plan.runtimeHarness.naReason) && plan.runtimeHarness.naReason.length >= 20 && /because/i.test(plan.runtimeHarness.naReason))) throw new TypeError("Invalid remediation evidence plan");
|
|
127
|
-
return structuredClone(plan);
|
|
128
|
-
}
|
|
129
|
-
export const plannedCommands = (plan: RemediationPlan) => [...plan.commands, ...(plan.runtimeHarness.command ? [plan.runtimeHarness.command] : []), plan.rollback.command];
|
|
130
|
-
// Launch-local scope only; task history is not an attempt authority.
|
|
131
|
-
export interface RemediationContext {
|
|
132
|
-
failedEvidenceRevision: string;
|
|
133
|
-
plan: RemediationPlan;
|
|
134
|
-
scope: RemediationScope;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export interface SddChangeSelection {
|
|
138
|
-
changeName: string;
|
|
139
|
-
workspaceRoot: string;
|
|
140
|
-
phase: "apply" | "verify" | "archive" | "remediate";
|
|
141
|
-
failedEvidenceRevision?: string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export const SDD_CHANGE_FLAG = "--gentle-sdd-change";
|
|
145
|
-
|
|
146
|
-
export const REMEDIATION_PLAN_ENV = "GENTLE_PI_SDD_REMEDIATION_PLAN";
|
|
147
|
-
|
|
148
120
|
export interface TaskRequest {
|
|
149
|
-
remediationIntent?: unknown;
|
|
150
|
-
sddRemediation?: RemediationContext;
|
|
151
|
-
sddPreflightContext?: string;
|
|
152
121
|
agent: AgentDefinition;
|
|
153
122
|
prompt: string;
|
|
154
123
|
label: string | undefined;
|
|
@@ -161,11 +130,18 @@ export interface TaskRequest {
|
|
|
161
130
|
sessionDir: string;
|
|
162
131
|
resumeSessionPath: string | undefined;
|
|
163
132
|
env: NodeJS.ProcessEnv;
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
// Untrusted narrowing intent; paths come only from matching host provenance.
|
|
167
|
-
researchSelection?: unknown;
|
|
133
|
+
// Host-provided only: the launcher's package injection signal (#1690) or
|
|
134
|
+
// the curated fallback. Never derived from tool input or agent definitions.
|
|
168
135
|
extensionPaths?: string[];
|
|
136
|
+
// Same host-only provenance as extensionPaths (#1690).
|
|
137
|
+
noExtensions?: boolean;
|
|
138
|
+
// Parsed `## Allowed edit surfaces` of a bounded writer. While the task is
|
|
139
|
+
// queued or running it claims them in `cwd`; run() rejects an overlapping
|
|
140
|
+
// claim (gentle-shell#1731). Read-only agents leave this unset.
|
|
141
|
+
writerSurfaces?: readonly string[];
|
|
142
|
+
// Canonical worktree root the claim is keyed to; the parent computes it before
|
|
143
|
+
// run() so admission stays synchronous. Defaults to `cwd`.
|
|
144
|
+
writerRoot?: string;
|
|
169
145
|
// Synchronous admission recheck at dequeue, before any OS spawn. Throws fail
|
|
170
146
|
// only this task; unlike onLaunch, it must never persist Changes evidence.
|
|
171
147
|
beforeSpawn?: () => void;
|
|
@@ -224,6 +200,8 @@ interface LiveTask {
|
|
|
224
200
|
processGroup: number | undefined;
|
|
225
201
|
terminal: { status: TaskRecord["status"]; error: string | null } | undefined;
|
|
226
202
|
childExit: number | null | undefined;
|
|
203
|
+
childExitSignal?: string | null;
|
|
204
|
+
instructionsTransportDir?: string;
|
|
227
205
|
cleanupDeadlineAt: number | undefined;
|
|
228
206
|
quarantined: boolean;
|
|
229
207
|
nextId: number;
|
|
@@ -236,6 +214,7 @@ interface LiveTask {
|
|
|
236
214
|
// live work, so the watchdog gives it the tool ceiling instead of the idle
|
|
237
215
|
// silence budget. Keyed by call id, holding the announced tool name.
|
|
238
216
|
inFlightTools: Map<string, string>;
|
|
217
|
+
argumentProgress: ToolArgumentProgress;
|
|
239
218
|
// Bounded ring buffer of the child's raw stderr output, capped to the last
|
|
240
219
|
// STDERR_TAIL_MAX characters. Only surfaced on the stall and pre-settle exit
|
|
241
220
|
// terminal paths, never on completed, cancelled, or other failure reasons.
|
|
@@ -243,9 +222,16 @@ interface LiveTask {
|
|
|
243
222
|
}
|
|
244
223
|
|
|
245
224
|
const STDERR_TAIL_MAX = 512;
|
|
225
|
+
const DIR_MODE = 0o700;
|
|
226
|
+
const FILE_MODE = 0o600;
|
|
227
|
+
// UTF-8 bytes, not characters: argv size is what kills the child on macOS.
|
|
228
|
+
export const MAX_INLINE_INSTRUCTIONS_BYTES = 1000;
|
|
229
|
+
const MAX_TRANSPORT_PREFIX_CHARS = 64;
|
|
246
230
|
const CHILD_MARKER = "GENTLE_PI_AGENTS_CHILD";
|
|
247
231
|
const IPC_MARKER = "GENTLE_PI_AGENTS_OWNED_IPC";
|
|
248
232
|
const PARENT_NOTIFICATION_TOOL = "subagent_parent_message";
|
|
233
|
+
/** The exact --tools list, so the child can report requested names Pi dropped silently (#1690). */
|
|
234
|
+
export const REQUESTED_TOOLS_ENV = "GENTLE_PI_AGENTS_REQUESTED_TOOLS";
|
|
249
235
|
const DEFAULT_TOOLS: readonly string[] = [];
|
|
250
236
|
const TERMINATION_GRACE_MS = 250;
|
|
251
237
|
const GROUP_CONFIRM_MS = 25;
|
|
@@ -277,31 +263,79 @@ function queryRejection(error: unknown): string {
|
|
|
277
263
|
return "parent rejected query";
|
|
278
264
|
}
|
|
279
265
|
|
|
266
|
+
export function formatChildExit(code: number | null | undefined, signal?: string | null): string {
|
|
267
|
+
if (typeof code === "number") return `code ${code}`;
|
|
268
|
+
if (signal) return `signal ${signal}`;
|
|
269
|
+
return `code ${code ?? "unknown"}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
280
272
|
const hostProcess: ProcessControl = { platform: process.platform, kill: (pid, signal) => process.kill(pid, signal) };
|
|
281
273
|
|
|
282
|
-
|
|
274
|
+
// The --tools value, or undefined when Pi keeps its default tools.
|
|
275
|
+
function requestedTools(request: TaskRequest): string | undefined {
|
|
276
|
+
const tools = request.agent.tools.length > 0 ? [...new Set([...request.agent.tools, PARENT_NOTIFICATION_TOOL])] : DEFAULT_TOOLS;
|
|
277
|
+
return tools.length > 0 ? tools.join(",") : undefined;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const WEB_TOOL_NAMES = ["web_enable", "web_search", "source_check", "fetch_content", "get_search_content"];
|
|
281
|
+
|
|
282
|
+
/** Resolve one already installed, explicitly requested package. Pi's own loader
|
|
283
|
+
* follows its manifest and supplies SDK aliases; no install or ambient discovery. */
|
|
284
|
+
function requestedWebPackagePaths(request: TaskRequest): string[] {
|
|
285
|
+
if (!WEB_TOOL_NAMES.every(name => request.agent.tools.includes(name))) return [];
|
|
286
|
+
try {
|
|
287
|
+
let agentDir = request.env.PI_CODING_AGENT_DIR;
|
|
288
|
+
if (!agentDir || /^~(?:$|[/\\])/.test(agentDir)) {
|
|
289
|
+
const home = (process.platform === "win32" ? request.env.USERPROFILE : request.env.HOME) || userInfo().homedir;
|
|
290
|
+
agentDir = agentDir ? agentDir.replace(/^~/, home) : join(home, ".pi", "agent");
|
|
291
|
+
}
|
|
292
|
+
const manager = new DefaultPackageManager({
|
|
293
|
+
cwd: request.cwd,
|
|
294
|
+
agentDir,
|
|
295
|
+
settingsManager: SettingsManager.inMemory(),
|
|
296
|
+
});
|
|
297
|
+
const installed = manager.getInstalledPath("npm:pi-web-access", "user");
|
|
298
|
+
return installed ? [realpathSync(installed)] : [];
|
|
299
|
+
} catch {
|
|
300
|
+
// Missing/incompatible package remains an observable child capability gap.
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function childArguments(request: TaskRequest, instructionsPath?: string): string[] {
|
|
283
306
|
const args = ["--mode", "rpc", "--session-dir", request.sessionDir];
|
|
284
|
-
|
|
285
|
-
|
|
307
|
+
const extensionPaths = [...new Set(request.extensionPaths ?? [])];
|
|
308
|
+
for (const packagePath of requestedWebPackagePaths(request)) {
|
|
309
|
+
const injected = extensionPaths.some(path => {
|
|
310
|
+
try { return realpathSync(resolve(request.cwd, path)) === packagePath; }
|
|
311
|
+
catch { return false; }
|
|
312
|
+
});
|
|
313
|
+
if (!injected) extensionPaths.push(packagePath);
|
|
314
|
+
}
|
|
315
|
+
args.push(...childPackageExtensionArgs({ noExtensions: request.noExtensions === true, extensionPaths }));
|
|
286
316
|
if (request.resumeSessionPath) args.push("--session", request.resumeSessionPath);
|
|
287
317
|
if (request.model) args.push("--model", request.thinking ? `${formatModelRef(request.model)}:${request.thinking}` : formatModelRef(request.model));
|
|
288
318
|
else if (request.thinking) args.push("--thinking", request.thinking);
|
|
289
|
-
const tools =
|
|
290
|
-
if (tools
|
|
291
|
-
if (
|
|
319
|
+
const tools = requestedTools(request);
|
|
320
|
+
if (tools !== undefined) args.push("--tools", tools);
|
|
321
|
+
if (instructionsPath) {
|
|
322
|
+
args.push("--append-system-prompt", instructionsPath);
|
|
323
|
+
} else if (request.agent.instructions.length > 0) {
|
|
324
|
+
args.push("--append-system-prompt", request.agent.instructions);
|
|
325
|
+
}
|
|
292
326
|
return args;
|
|
293
327
|
}
|
|
294
328
|
|
|
295
|
-
//
|
|
329
|
+
// Reuse the running pi entry while it exists; upgrades may remove it.
|
|
296
330
|
// GENTLE_PI_AGENTS_PI overrides it with a command line.
|
|
297
|
-
export function piCommand(proc: ProcessLike = process): PiCommand {
|
|
331
|
+
export function piCommand(proc: ProcessLike = process, exists: (path: string) => boolean = existsSync): PiCommand {
|
|
298
332
|
const override = proc.env.GENTLE_PI_AGENTS_PI?.trim();
|
|
299
333
|
if (override) {
|
|
300
334
|
const [command, ...args] = override.split(/\s+/);
|
|
301
335
|
return { command, args };
|
|
302
336
|
}
|
|
303
337
|
const entry = proc.argv[1];
|
|
304
|
-
if (entry && /(^|[\\/])cli\.js$/.test(entry)) return { command: proc.execPath, args: [entry] };
|
|
338
|
+
if (entry && /(^|[\\/])cli\.js$/.test(entry) && exists(entry)) return { command: proc.execPath, args: [entry] };
|
|
305
339
|
return { command: "pi", args: [] };
|
|
306
340
|
}
|
|
307
341
|
|
|
@@ -332,8 +366,7 @@ export class JsonLines {
|
|
|
332
366
|
}
|
|
333
367
|
|
|
334
368
|
export function promptText(request: TaskRequest): string {
|
|
335
|
-
|
|
336
|
-
return request.sddRemediation ? `${prompt}\n\n## Human-authorized remediation plan\nExecute only these exact commands in the selected cwd; report actual results without claiming native verification.\n${JSON.stringify(request.sddRemediation.plan)}\nFailed evidence: ${request.sddRemediation.failedEvidenceRevision}` : prompt;
|
|
369
|
+
return request.context ? `${request.prompt}\n\n## Context\n${request.context}` : request.prompt;
|
|
337
370
|
}
|
|
338
371
|
|
|
339
372
|
export class AgentRunner {
|
|
@@ -347,6 +380,7 @@ export class AgentRunner {
|
|
|
347
380
|
private readonly waiters = new Map<string, Array<(task: TaskRecord) => void>>();
|
|
348
381
|
private readonly queryWaiters = new Map<string, Array<(query: TaskQuery | undefined) => void>>();
|
|
349
382
|
private readonly firstQueries = new Map<string, TaskQuery>();
|
|
383
|
+
private readonly writers = new WriterSurfaceRegistry();
|
|
350
384
|
private counter = 0;
|
|
351
385
|
|
|
352
386
|
constructor(store: TaskStore, limits: RunnerLimits, deps: RunnerDeps, hooks: RunnerHooks) {
|
|
@@ -363,7 +397,6 @@ export class AgentRunner {
|
|
|
363
397
|
const task: TaskRecord = {
|
|
364
398
|
id: `${now.toString(36)}-${this.counter.toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
|
|
365
399
|
agent: request.agent.name,
|
|
366
|
-
...(request.sddPreflightContext ? { sddPreflightContext: request.sddPreflightContext } : {}),
|
|
367
400
|
mode: request.mode,
|
|
368
401
|
prompt: request.prompt,
|
|
369
402
|
label: taskLabel(request.prompt, request.label),
|
|
@@ -389,24 +422,16 @@ export class AgentRunner {
|
|
|
389
422
|
return task;
|
|
390
423
|
}
|
|
391
424
|
|
|
425
|
+
// Check and claim happen in this one synchronous call, so two launches can
|
|
426
|
+
// never both pass admission for overlapping surfaces; finish() releases.
|
|
392
427
|
run(request: TaskRequest): TaskRecord {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (request.sddRemediation) {
|
|
397
|
-
const active = [...this.queue.map(entry => entry.task), ...[...this.live.keys()].map(id => this.store.get(id))];
|
|
398
|
-
if (active.some(task => task?.agent === "sdd-remediate" && task.cwd === request.cwd)) {
|
|
399
|
-
throw new Error("Remediation already queued or running in this worktree; wait for confirmed cleanup or cancel the active task before requesting fresh authorization");
|
|
400
|
-
}
|
|
428
|
+
if (request.writerSurfaces) {
|
|
429
|
+
const conflicts = this.writers.conflicts(request.writerRoot ?? request.cwd, request.writerSurfaces);
|
|
430
|
+
if (conflicts.length) throw new Error(writerSurfaceConflictMessage(conflicts));
|
|
401
431
|
}
|
|
402
432
|
const task = this.createTask(request);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const launchRequest = {
|
|
406
|
-
...request,
|
|
407
|
-
sddChange: request.sddChange && { ...request.sddChange },
|
|
408
|
-
};
|
|
409
|
-
this.queue.push({ task, request: launchRequest });
|
|
433
|
+
if (request.writerSurfaces) this.writers.claim(task.id, request.writerRoot ?? request.cwd, request.writerSurfaces);
|
|
434
|
+
this.queue.push({ task, request });
|
|
410
435
|
queueMicrotask(() => this.pump());
|
|
411
436
|
return task;
|
|
412
437
|
}
|
|
@@ -445,6 +470,7 @@ export class AgentRunner {
|
|
|
445
470
|
if (live.queries.get(requestId) === query) {
|
|
446
471
|
query.cancel();
|
|
447
472
|
live.queries.delete(requestId);
|
|
473
|
+
this.hooks.onQuerySettled?.(id, requestId, "replied");
|
|
448
474
|
}
|
|
449
475
|
return accepted;
|
|
450
476
|
}
|
|
@@ -507,28 +533,55 @@ export class AgentRunner {
|
|
|
507
533
|
// one (`lib/rpc-host.ts`).
|
|
508
534
|
const env = withoutInteractiveHost({
|
|
509
535
|
...request.env,
|
|
510
|
-
...(request.extensionPaths ? { [RESEARCH_SELECTION_ENV]: JSON.stringify(request.researchSelection ?? null) } : {}),
|
|
511
536
|
[CHILD_MARKER]: "1",
|
|
512
537
|
[IPC_MARKER]: `${this.deps.now()}-${Math.random().toString(36).slice(2)}`,
|
|
513
538
|
...(hasParentPermissionChannel ? { GENTLE_PI_AGENTS_PARENT_PERMISSION_FD: "3" } : {}),
|
|
514
539
|
});
|
|
515
|
-
|
|
516
|
-
|
|
540
|
+
// Do not forward stale legacy child selection or authorization.
|
|
541
|
+
delete env.GENTLE_PI_SDD_REMEDIATION_PLAN;
|
|
542
|
+
delete env.GENTLE_PI_RESEARCH_SELECTION;
|
|
543
|
+
const tools = requestedTools(request);
|
|
544
|
+
if (tools !== undefined) env[REQUESTED_TOOLS_ENV] = tools;
|
|
545
|
+
else delete env[REQUESTED_TOOLS_ENV];
|
|
546
|
+
let instructionsTransportDir: string | undefined;
|
|
547
|
+
let instructionsTransportPath: string | undefined;
|
|
548
|
+
if (Buffer.byteLength(request.agent.instructions, "utf8") > MAX_INLINE_INSTRUCTIONS_BYTES) {
|
|
549
|
+
try {
|
|
550
|
+
const prefix = (request.agent.name || "instructions").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, MAX_TRANSPORT_PREFIX_CHARS);
|
|
551
|
+
instructionsTransportDir = mkdtempSync(join(tmpdir(), `gentle-pi-subagent-${prefix}-`));
|
|
552
|
+
try { chmodSync(instructionsTransportDir, DIR_MODE); } catch { /* best effort */ }
|
|
553
|
+
instructionsTransportPath = join(instructionsTransportDir, "instructions.md");
|
|
554
|
+
writeFileSync(instructionsTransportPath, request.agent.instructions, { mode: FILE_MODE, encoding: "utf8" });
|
|
555
|
+
try { chmodSync(instructionsTransportPath, FILE_MODE); } catch { /* best effort */ }
|
|
556
|
+
} catch (error) {
|
|
557
|
+
if (instructionsTransportDir) {
|
|
558
|
+
try { rmSync(instructionsTransportDir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
559
|
+
}
|
|
560
|
+
this.store.update(id, { status: TASK_STATUS.RUNNING, startedAt: this.deps.now(), lastStep: "starting" });
|
|
561
|
+
this.finish(id, TASK_STATUS.FAILED, `could not write agent instructions: ${error instanceof Error ? error.message : String(error)}`);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
517
565
|
let child: ChildLike;
|
|
518
566
|
try {
|
|
519
|
-
|
|
567
|
+
const pi = this.deps.resolvePi?.() ?? this.deps.pi;
|
|
568
|
+
child = this.deps.spawn(pi.command, [...pi.args, ...childArguments(request, instructionsTransportPath)], {
|
|
520
569
|
cwd: request.cwd,
|
|
521
570
|
env,
|
|
522
571
|
detached,
|
|
572
|
+
windowsHide: true,
|
|
523
573
|
stdio: hasParentPermissionChannel ? ["pipe", "pipe", "pipe", permissionChannelStdio, "ipc"] : ["pipe", "pipe", "pipe", "ipc"],
|
|
524
574
|
});
|
|
525
575
|
} catch (error) {
|
|
576
|
+
if (instructionsTransportDir) {
|
|
577
|
+
try { rmSync(instructionsTransportDir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
578
|
+
}
|
|
526
579
|
this.store.update(id, { status: TASK_STATUS.RUNNING, startedAt: this.deps.now(), lastStep: "starting" });
|
|
527
580
|
this.finish(id, TASK_STATUS.FAILED, `could not start pi: ${error instanceof Error ? error.message : String(error)}`);
|
|
528
581
|
return;
|
|
529
582
|
}
|
|
530
583
|
const processGroup = detached && typeof child.pid === "number" && child.pid > 0 ? child.pid : undefined;
|
|
531
|
-
const live: LiveTask = { child, sawRunEvent: false, mutationStarts: new Map(), inFlightTools: new Map(), pending: new Map(), queries: new Map(), replies: new Map(), cancelStall: () => {}, cancelGrace: () => {}, processGroup, terminal: undefined, childExit: undefined, cleanupDeadlineAt: undefined, quarantined: false, nextId: 0, ipcClosed: false, acknowledgedIpcIds: new Set(), acknowledgedIpcOrder: [], stderrTail: "" };
|
|
584
|
+
const live: LiveTask = { child, sawRunEvent: false, mutationStarts: new Map(), inFlightTools: new Map(), argumentProgress: new ToolArgumentProgress(), pending: new Map(), queries: new Map(), replies: new Map(), cancelStall: () => {}, cancelGrace: () => {}, processGroup, terminal: undefined, childExit: undefined, childExitSignal: undefined, instructionsTransportDir, cleanupDeadlineAt: undefined, quarantined: false, nextId: 0, ipcClosed: false, acknowledgedIpcIds: new Set(), acknowledgedIpcOrder: [], stderrTail: "" };
|
|
532
585
|
if (request.prepareResponseObservations) {
|
|
533
586
|
let ready = false;
|
|
534
587
|
live.observationPreparation = () => ready;
|
|
@@ -542,7 +595,7 @@ export class AgentRunner {
|
|
|
542
595
|
}
|
|
543
596
|
this.live.set(id, live);
|
|
544
597
|
const permissionPipe = child.stdio?.[3];
|
|
545
|
-
if (hasParentPermissionChannel && permissionPipe
|
|
598
|
+
if (hasParentPermissionChannel && permissionPipe instanceof Duplex) {
|
|
546
599
|
live.permissionBroker = new ParentStandingReviewPermissionBroker(
|
|
547
600
|
{ readable: permissionPipe, writable: permissionPipe },
|
|
548
601
|
(repositoryIdentity) => this.live.get(id) === live && !live.terminal && request.authorizeParentStandingReviewPermission?.(repositoryIdentity) === true,
|
|
@@ -560,6 +613,11 @@ export class AgentRunner {
|
|
|
560
613
|
try { request.onLaunch?.(); }
|
|
561
614
|
catch (error) { this.requestStop(id, TASK_STATUS.FAILED, `could not register launched worktree: ${error instanceof Error ? error.message : String(error)}`); }
|
|
562
615
|
});
|
|
616
|
+
child.on("exit", (code, signal) => this.exited(id, code, signal));
|
|
617
|
+
if (!child.stdin || !child.stdout) {
|
|
618
|
+
this.requestStop(id, TASK_STATUS.FAILED, "could not start pi: missing RPC streams");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
563
621
|
child.stdin.on("error", () => {});
|
|
564
622
|
this.armStall(id, live);
|
|
565
623
|
const lines = new JsonLines((value) => this.receive(id, request, value));
|
|
@@ -570,11 +628,6 @@ export class AgentRunner {
|
|
|
570
628
|
const tail = live.stderrTail + chunk;
|
|
571
629
|
live.stderrTail = tail.length > STDERR_TAIL_MAX ? tail.slice(-STDERR_TAIL_MAX) : tail;
|
|
572
630
|
});
|
|
573
|
-
child.on("exit", (code) => this.exited(id, code));
|
|
574
|
-
if (request.sddRemediation && child.pid === undefined) {
|
|
575
|
-
this.childError(id, new Error("remediation child has no process ID"));
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
631
|
void this.send(id, { type: "get_state" }).then((response) => {
|
|
579
632
|
const data = response.data as { sessionFile?: unknown; model?: { provider?: unknown; id?: unknown } | null; thinkingLevel?: unknown } | undefined;
|
|
580
633
|
if (response.success !== true || live.terminal || this.live.get(id) !== live || !data) return;
|
|
@@ -613,8 +666,8 @@ export class AgentRunner {
|
|
|
613
666
|
|
|
614
667
|
// An idle child is bounded by the silence budget; a child whose announced
|
|
615
668
|
// tool call is still running is live work and bounded by the longer tool
|
|
616
|
-
// ceiling.
|
|
617
|
-
//
|
|
669
|
+
// ceiling. These are renewable silence budgets, not total duration limits.
|
|
670
|
+
// A finished tool call returns the task to idle silence.
|
|
618
671
|
private armStall(id: string, live: LiveTask): void {
|
|
619
672
|
live.cancelStall();
|
|
620
673
|
const tool = live.inFlightTools.values().next().value;
|
|
@@ -668,12 +721,12 @@ export class AgentRunner {
|
|
|
668
721
|
try {
|
|
669
722
|
if (live.queries.has(parsed.frame.id)) rejectQuery("duplicate query request");
|
|
670
723
|
if (live.queries.size >= CHILD_QUERY_MAX_INFLIGHT) rejectQuery("too many pending parent queries");
|
|
671
|
-
query = { replying: false, cancel: this.deps.schedule(() => this.expireQuery(live, parsed.frame!.id), CHILD_QUERY_TIMEOUT_MS) };
|
|
724
|
+
query = { replying: false, cancel: this.deps.schedule(() => this.expireQuery(id, live, parsed.frame!.id), CHILD_QUERY_TIMEOUT_MS) };
|
|
672
725
|
live.queries.set(parsed.frame.id, query);
|
|
673
726
|
if (!this.hooks.onQuery) rejectQuery("task parent cannot accept queries");
|
|
674
727
|
if (this.hooks.onQuery(task, parsed.frame.id, parsed.frame.message) === false) rejectQuery("task parent is not the active host session");
|
|
675
728
|
if (task.mode === AGENT_MODE.TASK && !this.firstQueries.has(id)) {
|
|
676
|
-
const first = { taskId: id, requestId: parsed.frame.id };
|
|
729
|
+
const first = { taskId: id, requestId: parsed.frame.id, message: parsed.frame.message };
|
|
677
730
|
this.firstQueries.set(id, first);
|
|
678
731
|
for (const resolve of this.queryWaiters.get(id) ?? []) resolve(first);
|
|
679
732
|
this.queryWaiters.delete(id);
|
|
@@ -687,10 +740,11 @@ export class AgentRunner {
|
|
|
687
740
|
}
|
|
688
741
|
}
|
|
689
742
|
|
|
690
|
-
private expireQuery(live: LiveTask, id: string): void {
|
|
743
|
+
private expireQuery(taskId: string, live: LiveTask, id: string): void {
|
|
691
744
|
const query = live.queries.get(id);
|
|
692
745
|
if (!query) return;
|
|
693
746
|
live.queries.delete(id);
|
|
747
|
+
this.hooks.onQuerySettled?.(taskId, id, "expired");
|
|
694
748
|
if (query.replying) this.settleReply(live, id, false);
|
|
695
749
|
else this.sendQueryError(live, id, "parent query timed out");
|
|
696
750
|
}
|
|
@@ -743,7 +797,7 @@ export class AgentRunner {
|
|
|
743
797
|
|
|
744
798
|
private write(live: LiveTask, payload: Record<string, unknown>): void {
|
|
745
799
|
try {
|
|
746
|
-
live.child.stdin
|
|
800
|
+
live.child.stdin?.write(`${JSON.stringify(payload)}\n`);
|
|
747
801
|
} catch {
|
|
748
802
|
// the child is gone; the exit handler settles the task
|
|
749
803
|
}
|
|
@@ -782,7 +836,12 @@ export class AgentRunner {
|
|
|
782
836
|
// renews the watchdog here, so fire-and-forget UI traffic that normalizes
|
|
783
837
|
// to nothing cannot keep a child that never started its run alive forever
|
|
784
838
|
// (#1034); the pre-existing timer stays armed until real progress arrives.
|
|
785
|
-
const
|
|
839
|
+
const argumentProgress = live.argumentProgress.observe(raw);
|
|
840
|
+
if (argumentProgress) {
|
|
841
|
+
live.sawRunEvent = true;
|
|
842
|
+
this.store.update(id, { lastStep: "generating tool arguments", lastActivityAt: this.deps.now() });
|
|
843
|
+
}
|
|
844
|
+
const progress = events.length > 0 || argumentProgress;
|
|
786
845
|
for (const event of events) {
|
|
787
846
|
if (event.type === TASK_EVENT.RESPONSE_OBSERVATION) {
|
|
788
847
|
const buffer = live.observations;
|
|
@@ -906,6 +965,7 @@ export class AgentRunner {
|
|
|
906
965
|
if (this.deps.now() >= (live.cleanupDeadlineAt ?? 0)) {
|
|
907
966
|
live.cancelGrace();
|
|
908
967
|
live.quarantined = true;
|
|
968
|
+
this.cleanupLive(live);
|
|
909
969
|
this.finish(id, TASK_STATUS.FAILED, `process cleanup unconfirmed after ${GROUP_CONFIRM_DEADLINE_MS}ms; capacity quarantined`, live);
|
|
910
970
|
return;
|
|
911
971
|
}
|
|
@@ -930,12 +990,24 @@ export class AgentRunner {
|
|
|
930
990
|
if (this.deps.now() >= (live.cleanupDeadlineAt ?? 0)) {
|
|
931
991
|
live.cancelGrace();
|
|
932
992
|
live.quarantined = true;
|
|
993
|
+
this.cleanupLive(live);
|
|
933
994
|
this.finish(id, TASK_STATUS.FAILED, `child exit unconfirmed after ${GROUP_CONFIRM_DEADLINE_MS}ms; capacity quarantined`, live);
|
|
934
995
|
return;
|
|
935
996
|
}
|
|
936
997
|
live.cancelGrace = this.deps.schedule(() => this.confirmGroupExit(id, live), GROUP_CONFIRM_MS);
|
|
937
998
|
}
|
|
938
999
|
|
|
1000
|
+
private cleanupLive(live: LiveTask): void {
|
|
1001
|
+
live.permissionBroker?.close();
|
|
1002
|
+
this.closeIpc(live);
|
|
1003
|
+
live.cancelStall();
|
|
1004
|
+
live.cancelGrace();
|
|
1005
|
+
if (live.instructionsTransportDir) {
|
|
1006
|
+
try { rmSync(live.instructionsTransportDir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
1007
|
+
live.instructionsTransportDir = undefined;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
939
1011
|
private childError(id: string, error: Error): void {
|
|
940
1012
|
const live = this.live.get(id);
|
|
941
1013
|
if (!live) return;
|
|
@@ -945,41 +1017,42 @@ export class AgentRunner {
|
|
|
945
1017
|
this.requestStop(id, TASK_STATUS.FAILED, `pi process error: ${error.message}`);
|
|
946
1018
|
return;
|
|
947
1019
|
}
|
|
948
|
-
|
|
949
|
-
this.closeIpc(live);
|
|
950
|
-
live.cancelStall();
|
|
951
|
-
live.cancelGrace();
|
|
1020
|
+
this.cleanupLive(live);
|
|
952
1021
|
this.live.delete(id);
|
|
953
1022
|
this.finish(id, TASK_STATUS.FAILED, `could not start pi: ${error.message}`, live);
|
|
954
1023
|
}
|
|
955
1024
|
|
|
956
|
-
private exited(id: string, code: number | null): void {
|
|
1025
|
+
private exited(id: string, code: number | null, signal?: NodeJS.Signals | string | null): void {
|
|
957
1026
|
const live = this.live.get(id);
|
|
958
1027
|
if (!live) return;
|
|
959
1028
|
live.childExit = code;
|
|
1029
|
+
live.childExitSignal = signal ?? null;
|
|
1030
|
+
const exitDesc = formatChildExit(code, signal);
|
|
960
1031
|
if (this.groupExists(live)) {
|
|
961
|
-
if (!live.terminal) this.requestStop(id, TASK_STATUS.FAILED, `pi exited with
|
|
1032
|
+
if (!live.terminal) this.requestStop(id, TASK_STATUS.FAILED, `pi exited with ${exitDesc} before agent_settled${this.stderrSuffix(live)}`);
|
|
962
1033
|
return;
|
|
963
1034
|
}
|
|
964
1035
|
this.completeExit(id, live);
|
|
965
1036
|
}
|
|
966
1037
|
|
|
967
1038
|
private completeExit(id: string, live: LiveTask): void {
|
|
968
|
-
|
|
969
|
-
this.closeIpc(live);
|
|
970
|
-
live.cancelStall();
|
|
971
|
-
live.cancelGrace();
|
|
1039
|
+
this.cleanupLive(live);
|
|
972
1040
|
this.live.delete(id);
|
|
973
1041
|
// Quarantine already notified completion, but its retained slot is now free.
|
|
974
1042
|
if (live.quarantined) {
|
|
1043
|
+
this.writers.release(id);
|
|
975
1044
|
queueMicrotask(() => this.pump());
|
|
976
1045
|
return;
|
|
977
1046
|
}
|
|
978
1047
|
const terminal = live.terminal;
|
|
979
|
-
|
|
1048
|
+
const exitDesc = formatChildExit(live.childExit, live.childExitSignal);
|
|
1049
|
+
this.finish(id, terminal ? terminal.status : TASK_STATUS.FAILED, terminal ? terminal.error : `pi exited with ${exitDesc} before agent_settled${this.stderrSuffix(live)}`, live);
|
|
980
1050
|
}
|
|
981
1051
|
|
|
982
1052
|
private finish(id: string, status: TaskRecord["status"], error: string | null, live?: LiveTask): void {
|
|
1053
|
+
// A quarantined child may still be writing: its claim lives as long as its
|
|
1054
|
+
// reserved slot and is released in completeExit on proven exit.
|
|
1055
|
+
if (!live?.quarantined) this.writers.release(id);
|
|
983
1056
|
const current = this.store.get(id);
|
|
984
1057
|
if (!current || isFinished(current.status)) return;
|
|
985
1058
|
const finished = this.store.update(id, { status, endedAt: this.deps.now(), error, lastStep: error ?? "done" });
|
package/lib/agents-view.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { emptyThread, isFinished, TASK_STATUS, type TaskRecord, type TaskStore,
|
|
|
5
5
|
import { renderThreadItem, type AgentsThreadTheme } from "./agents-thread-view.ts";
|
|
6
6
|
import { formatElapsed } from "./agents-widget.ts";
|
|
7
7
|
import { createNativePointerScope, type NativePointerRegion } from "./native-pointer-region.ts";
|
|
8
|
-
import { formatTokens } from "./shell-bar.ts";
|
|
8
|
+
import { formatCost, formatTokens } from "./shell-bar.ts";
|
|
9
9
|
import { PresenceCursor, readActivity, type Header, type Target } from "./orchestrator-presence.ts";
|
|
10
10
|
|
|
11
11
|
// Gentle Agents overlay: tasks on the left, the selected task's thread on
|
|
@@ -128,8 +128,22 @@ function fit(text: string, width: number): string {
|
|
|
128
128
|
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// Deep equality over presence thread items: validated JSON payloads, so only
|
|
132
|
+
// plain objects, arrays, strings, booleans, and null appear. Used to keep the
|
|
133
|
+
// prior item object (and its WeakMap render cache entry) when polled content
|
|
134
|
+
// is render-equivalent at the same position.
|
|
135
|
+
function sameItemValue(a: unknown, b: unknown): boolean {
|
|
136
|
+
if (a === b) return true;
|
|
137
|
+
if (typeof a !== "object" || typeof b !== "object" || !a || !b) return false;
|
|
138
|
+
const left = a as Record<string, unknown>;
|
|
139
|
+
const right = b as Record<string, unknown>;
|
|
140
|
+
const keys = Object.keys(left);
|
|
141
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
142
|
+
return keys.every((key) => Object.hasOwn(right, key) && sameItemValue(left[key], right[key]));
|
|
143
|
+
}
|
|
144
|
+
|
|
131
145
|
export function taskHeader(task: TaskRecord, now: number): string {
|
|
132
|
-
const parts = [task.agent, task.status, task.model, task.tokens > 0 ? formatTokens(task.tokens) : "", task.cost > 0 ?
|
|
146
|
+
const parts = [task.agent, task.status, task.model, task.tokens > 0 ? formatTokens(task.tokens) : "", task.cost > 0 ? formatCost(task.cost) : "", task.startedAt === null ? "" : formatElapsed((task.endedAt ?? now) - task.startedAt)];
|
|
133
147
|
return parts.filter((part) => part.length > 0).join(" · ");
|
|
134
148
|
}
|
|
135
149
|
|
|
@@ -197,8 +211,17 @@ export class AgentsView {
|
|
|
197
211
|
const task: TaskRecord = { ...summary, id: `peer:${id}:${summary.id}`, parentSessionId: id,
|
|
198
212
|
status: summary.status as TaskRecord["status"], mode: "background", prompt: "", cwd: "",
|
|
199
213
|
thinking: undefined, sessionPath: null, error: null, result: null, lastStep: "", turns: 0, toolCalls: 0, tokens: 0, cost: 0 };
|
|
214
|
+
const prior = this.remoteThreads.get(task.id);
|
|
200
215
|
threads.set(task.id, { ...emptyThread(), ...thread,
|
|
201
|
-
items: thread.items.map((item) =>
|
|
216
|
+
items: thread.items.map((item, index) => {
|
|
217
|
+
// Sanitize before equality: presence tool items carry no invocation
|
|
218
|
+
// args, so compare against the stored sanitized form.
|
|
219
|
+
const sanitized = item.kind === "tool" ? { ...item, args: {} } : item;
|
|
220
|
+
const previous = prior?.items[index];
|
|
221
|
+
// Reuse the prior object when content is render-equivalent so the
|
|
222
|
+
// WeakMap render cache hits; any delta keeps a fresh identity.
|
|
223
|
+
return previous !== undefined && sameItemValue(previous, sanitized) ? previous : sanitized;
|
|
224
|
+
}) as ThreadItem[] });
|
|
202
225
|
return task;
|
|
203
226
|
});
|
|
204
227
|
groups.push({ id, sessionId: id, label: `${header.label}${unavailable ? " · unavailable" : ""}`, tasks });
|