gentle-pi 4.2.0 → 4.4.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 +340 -138
- package/assets/agents/gentle-ai-explore.md +1 -0
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -1
- package/assets/design-canvas/canvas.css +648 -0
- package/assets/design-canvas/canvas.js +1497 -0
- package/assets/design-canvas/index.html +88 -0
- package/assets/design-canvas/inspector.js +290 -0
- package/assets/orchestrator-delegation-reference.md +163 -0
- package/assets/orchestrator-delegation.md +25 -176
- package/assets/orchestrator-tracking.md +1 -1
- package/assets/orchestrator-verification.md +14 -8
- package/assets/orchestrator-writer.md +5 -1
- package/assets/orchestrator.md +11 -11
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +237 -139
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
- package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/assets/diagrams/agent-orchestration.svg +54 -15
- package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
- package/docs/assets/diagrams/measured-routing.svg +49 -0
- package/docs/assets/diagrams/native-review.svg +52 -12
- package/docs/assets/diagrams/odd-workflow.svg +128 -66
- package/docs/assets/diagrams/permission-modes.svg +51 -0
- package/docs/assets/features/audio-notifications.png +0 -0
- package/docs/assets/features/command-palette.png +0 -0
- package/docs/bundled-install.md +65 -30
- package/docs/delegated-verification.md +16 -8
- package/docs/gentle-agents-activity.md +16 -6
- package/docs/gentle-music.md +61 -0
- package/docs/gentle-shell-uninstall.md +8 -1
- package/docs/gentle-shell.md +80 -9
- package/docs/install-wizard.md +37 -6
- package/docs/permission-modes.md +8 -3
- package/docs/readme-reference.md +120 -28
- package/docs/session-profile-format.md +26 -0
- package/docs/voice.md +259 -0
- package/docs/yolo-mode.md +38 -10
- package/extensions/ask-user-choice.ts +4 -1
- package/extensions/codegraph-tools.ts +68 -6
- package/extensions/gentle-agents.ts +270 -48
- package/extensions/gentle-ai.ts +490 -113
- package/extensions/gentle-design.ts +158 -0
- package/extensions/gentle-herdr-activity.ts +2 -3
- package/extensions/gentle-music.ts +289 -0
- package/extensions/gentle-shell.ts +359 -85
- package/extensions/gentle-todo.ts +43 -9
- package/extensions/gentle-voice.ts +11 -0
- package/extensions/quiet-tools.ts +1 -1
- package/extensions/session-scratch.ts +384 -0
- package/extensions/skill-registry.ts +63 -8
- package/extensions/startup-banner.ts +2 -3
- package/lib/agent-home.ts +8 -3
- package/lib/agent-profiles.ts +26 -0
- package/lib/agents-config.ts +35 -0
- package/lib/agents-protocol.ts +60 -1
- package/lib/agents-runner.ts +107 -9
- package/lib/agents-session-transport.ts +101 -6
- package/lib/agents-view.ts +3 -1
- package/lib/agents-widget.ts +14 -1
- package/lib/auto-permission-memory.ts +48 -18
- package/lib/auto-permission-runtime.ts +21 -6
- package/lib/bounded-writer-admission.ts +51 -0
- package/lib/clipboard-image.ts +147 -0
- package/lib/command-palette-catalog.ts +2 -4
- package/lib/commit-slice-signal.ts +100 -0
- package/lib/context-backstop.ts +120 -0
- package/lib/delivered-test-lint.ts +80 -0
- package/lib/design-canvas-server.ts +947 -0
- package/lib/design-comments.ts +437 -0
- package/lib/design-handoffs.ts +313 -0
- package/lib/design-open-browser.ts +58 -0
- package/lib/design-sources.ts +120 -0
- package/lib/destructive-command-guard.ts +120 -9
- package/lib/external-editor.ts +31 -0
- package/lib/gauge-theme.ts +146 -0
- package/lib/gentle-ai-binary.ts +28 -7
- package/lib/gentle-design.ts +513 -0
- package/lib/gentle-shell-bundled.ts +146 -36
- package/lib/gentle-shell-import-pi.ts +233 -0
- package/lib/gentle-shell-launcher.ts +154 -15
- package/lib/gentle-shell-startup-update.ts +61 -9
- package/lib/gentle-shell-uninstall.ts +51 -2
- package/lib/herdr-activity.ts +24 -0
- package/lib/image-attachments.ts +150 -0
- package/lib/inprocess-reviewer.ts +33 -2
- package/lib/nan-provider.ts +3 -0
- package/lib/native-review-cli.ts +20 -5
- package/lib/notification-audio-native.ts +149 -13
- package/lib/notification-audio.ts +4 -0
- package/lib/notification-pulse-worker.ts +82 -18
- package/lib/notification-service.ts +5 -1
- package/lib/orchestrator-discovery.ts +5 -1
- package/lib/orchestrator-presence.ts +87 -8
- package/lib/orchestrator-scope.ts +31 -3
- package/lib/orchestrator-state.ts +11 -3
- package/lib/permission-mode-policy.ts +2 -2
- package/lib/provider-contract-bundle.ts +99 -53
- package/lib/review-candidate-view-owner.ts +29 -19
- package/lib/review-candidate-view.ts +36 -2
- package/lib/review-host-relay.ts +11 -2
- package/lib/review-reminder-receipt.ts +24 -1
- package/lib/review-risk-assessment.ts +43 -38
- package/lib/review-session-standing-permission.ts +3 -1
- package/lib/session-profile-append-controller.ts +713 -0
- package/lib/session-profile-disk-reader.ts +182 -0
- package/lib/session-profile-freeze.ts +30 -1
- package/lib/session-profile-persistence.ts +15 -7
- package/lib/session-scratch.ts +523 -0
- package/lib/session-worktree-registry.ts +7 -0
- package/lib/shell-bar.ts +29 -28
- package/lib/shell-changes-view.ts +80 -13
- package/lib/shell-changes.ts +81 -5
- package/lib/shell-gauge.ts +49 -6
- package/lib/shell-prompt.ts +4 -4
- package/lib/shell-sidebar-layout.ts +26 -6
- package/lib/shell-todo.ts +24 -5
- package/lib/shell-usage-view.ts +5 -1
- package/lib/shell-usage.ts +7 -7
- package/lib/verifier-admission.ts +30 -0
- package/lib/vim-editor-adapter.ts +11 -8
- package/lib/voice-artifacts.ts +188 -0
- package/lib/voice-command.ts +576 -0
- package/lib/voice-install.ts +455 -0
- package/lib/voice-platform.ts +99 -0
- package/lib/voice-push-to-talk.ts +262 -0
- package/lib/voice-session.ts +460 -0
- package/lib/voice-settings.ts +50 -0
- package/lib/voice-ui.ts +158 -0
- package/lib/yolo-session-policy.ts +58 -22
- package/lib/yolo-temp-scratch.ts +69 -0
- package/package.json +1 -2
- package/runtime/gentle-ai-binary.mjs +28 -7
- package/runtime/gentle-shell-bundled.mjs +145 -35
- package/runtime/gentle-shell-import-pi.mjs +234 -0
- package/runtime/gentle-shell-launcher.mjs +152 -13
- package/runtime/gentle-shell-startup-update.mjs +61 -9
- package/runtime/gentle-shell-uninstall.mjs +51 -2
- package/runtime/native-review-cli.mjs +19 -4
- package/runtime/review-risk-assessment.mjs +43 -38
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/bundled-engram-migration.mjs +119 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/installer-downloads.mjs +7 -7
- package/scripts/installer-preflight.mjs +5 -3
- package/scripts/installer-server.mjs +14 -4
- package/scripts/installer-windows.mjs +37 -7
- package/scripts/main-channel.mjs +94 -19
- package/scripts/pi-update.mjs +34 -6
- package/scripts/run-test-suite.mjs +5 -0
- package/scripts/verify-package-files.mjs +43 -13
- package/scripts/voice-worker.cjs +476 -0
- package/skills/design/SKILL.md +110 -0
- package/skills/gentle-ai/SKILL.md +3 -3
- package/skills/work-unit-commits/SKILL.md +5 -3
- package/tests/agent-profiles.test.ts +24 -0
- package/tests/agent-risk-escalation.test.ts +37 -10
- package/tests/agents-config.test.ts +61 -0
- package/tests/agents-fake-child.ts +8 -1
- package/tests/agents-protocol.test.ts +32 -0
- package/tests/agents-runner.test.ts +485 -7
- package/tests/agents-session-transport.test.ts +53 -3
- package/tests/ask-user-choice.test.ts +30 -0
- package/tests/auto-mode.test.ts +17 -0
- package/tests/auto-permission-memory-diagnostics.test.ts +56 -0
- package/tests/auto-permission-runtime.test.ts +85 -1
- package/tests/autonomous-guard.test.ts +142 -0
- package/tests/bounded-writer-admission.test.ts +11 -3
- package/tests/bundled-install.test.ts +3 -3
- package/tests/card-content-policy.test.ts +19 -9
- package/tests/child-safety.test.ts +39 -0
- package/tests/clipboard-image.test.ts +114 -0
- package/tests/codegraph-tools.test.ts +123 -2
- package/tests/command-palette.test.ts +15 -5
- package/tests/commit-slice-signal.test.ts +162 -0
- package/tests/context-backstop.test.ts +225 -0
- package/tests/delegate-for-reason-contract.test.ts +92 -24
- package/tests/delivered-test-lint.test.ts +105 -0
- package/tests/design-canvas-assets.test.ts +710 -0
- package/tests/design-canvas-server.test.ts +1096 -0
- package/tests/design-comments.test.ts +201 -0
- package/tests/design-handoffs.test.ts +282 -0
- package/tests/design-open-browser.test.ts +70 -0
- package/tests/design-sources.test.ts +132 -0
- package/tests/destructive-command-guard.test.ts +85 -1
- package/tests/engram-safety.test.ts +132 -57
- package/tests/external-editor.test.ts +84 -0
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
- package/tests/gauge-theme.test.ts +120 -0
- package/tests/gentle-agents.test.ts +645 -21
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-child-guards.test.ts +61 -0
- package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-ai.test.ts +292 -13
- package/tests/gentle-design.test.ts +1313 -0
- package/tests/gentle-music.test.ts +335 -0
- package/tests/gentle-shell-bin.test.ts +163 -0
- package/tests/gentle-shell-bundled.test.ts +392 -176
- package/tests/gentle-shell-import-pi.test.ts +262 -0
- package/tests/gentle-shell-launcher.test.ts +107 -2
- package/tests/gentle-shell-startup-update.test.ts +77 -1
- package/tests/gentle-shell-uninstall.test.ts +89 -0
- package/tests/gentle-shell.test.ts +225 -17
- package/tests/gentle-todo.test.ts +190 -0
- package/tests/headless-delivery-question.test.ts +58 -0
- package/tests/herdr-activity.test.ts +34 -1
- package/tests/image-attachments.test.ts +119 -0
- package/tests/image-paste-chips.test.ts +236 -0
- package/tests/inprocess-reviewer.test.ts +55 -2
- package/tests/installer-bundles.test.ts +12 -6
- package/tests/installer-posix-bootstrap.test.ts +8 -8
- package/tests/installer-preflight.test.ts +16 -0
- package/tests/installer-server.test.ts +10 -2
- package/tests/installer-windows-bootstrap.test.ts +94 -7
- package/tests/installer-windows-diagnostics.test.ts +111 -0
- package/tests/main-channel-upgrade.test.ts +232 -2
- package/tests/main-channel.test.ts +140 -21
- package/tests/nan-provider.test.ts +36 -0
- package/tests/native-review-capability-contract.test.ts +17 -4
- package/tests/native-review-parity.test.ts +23 -2
- package/tests/no-self-pause-contract.test.ts +5 -2
- package/tests/notification-audio-native.test.ts +128 -9
- package/tests/notification-customize.test.ts +17 -5
- package/tests/odd-integration.test.ts +5 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +128 -162
- package/tests/odd-routing-contract.test.ts +115 -30
- package/tests/orchestrator-budget.test.ts +22 -8
- package/tests/orchestrator-discovery.test.ts +29 -0
- package/tests/orchestrator-presence.test.ts +100 -2
- package/tests/orchestrator-rdd-ownership.test.ts +3 -1
- package/tests/orchestrator-scope.test.ts +80 -0
- package/tests/orchestrator-state.test.ts +29 -0
- package/tests/orchestrator-status-publication-contract.test.ts +12 -8
- package/tests/package-files.test.ts +34 -0
- package/tests/package-manifest.test.ts +24 -20
- package/tests/permission-mode-policy.test.ts +10 -1
- package/tests/pi-update-launcher.test.ts +21 -2
- package/tests/pi-update.test.ts +99 -4
- package/tests/primary-session-prompt.test.ts +101 -0
- package/tests/provider-contract-bundle.test.ts +118 -0
- package/tests/provider-contract-mirror.test.ts +24 -0
- package/tests/quiet-tool-rendering.test.ts +23 -0
- package/tests/rdd-aware-verification-contract.test.ts +29 -11
- package/tests/rdd-status-line.test.ts +79 -0
- package/tests/review-agent-end-preflight.test.ts +6 -0
- package/tests/review-candidate-view.test.ts +49 -0
- package/tests/review-contract-prompt.test.ts +5 -2
- package/tests/review-controller-native-routing.test.ts +14 -1
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +147 -3
- package/tests/review-host-relay.test.ts +9 -2
- package/tests/review-json-arguments.test.ts +20 -3
- package/tests/review-loadout-rdd-off-sdk.test.ts +258 -0
- package/tests/review-owner-identity.test.ts +132 -0
- package/tests/review-relay-transport-agent.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +31 -1
- package/tests/review-risk-assessment.test.ts +163 -76
- package/tests/review-session-standing-permission-controller.test.ts +14 -1
- package/tests/review-session-standing-permission.test.ts +27 -1
- package/tests/run-test-suite.test.ts +15 -0
- package/tests/runtime-harness.mjs +16 -6
- package/tests/session-changes-shell.test.ts +1 -1
- package/tests/session-changes.test.ts +2 -1
- package/tests/session-profile-append-controller-fork.test.ts +458 -0
- package/tests/session-profile-append-controller-pi.test.ts +290 -0
- package/tests/session-profile-append-controller.test.ts +731 -0
- package/tests/session-profile-disk-reader-pi.test.ts +183 -0
- package/tests/session-profile-disk-reader.test.ts +311 -0
- package/tests/session-profile-freeze.test.ts +23 -0
- package/tests/session-scratch.test.ts +1011 -0
- package/tests/session-subject-routing-contract.test.ts +2 -1
- package/tests/session-worktree-registry.test.ts +13 -1
- package/tests/shell-bar.test.ts +21 -2
- package/tests/shell-changes-view.test.ts +137 -1
- package/tests/shell-changes.test.ts +148 -1
- package/tests/shell-gauge.test.ts +42 -0
- package/tests/shell-prompt.test.ts +3 -3
- package/tests/shell-render-cache.test.ts +134 -1
- package/tests/shell-sidebar-layout.test.ts +50 -0
- package/tests/short-check-routing-contract.test.ts +1 -1
- package/tests/skill-collision-prefixes.test.ts +3 -1
- package/tests/skill-registry-watcher.test.ts +17 -5
- package/tests/skill-registry.test.ts +47 -3
- package/tests/small-task-inline-contract.test.ts +37 -0
- package/tests/standalone-config-home.test.ts +324 -0
- package/tests/startup-banner.test.ts +5 -4
- package/tests/startup-home-repo.test.ts +34 -0
- package/tests/support/orchestrator-modules.ts +1 -0
- package/tests/task-size-routing-contract.test.ts +70 -10
- package/tests/test-scratch-contract.test.ts +102 -0
- package/tests/test-strength-contract.test.ts +36 -1
- package/tests/tool-batching-contract.test.ts +64 -0
- package/tests/verification-proportionality.test.ts +46 -0
- package/tests/verifier-admission.test.ts +154 -0
- package/tests/verify-agent-contract.test.ts +8 -8
- package/tests/voice-command.test.ts +773 -0
- package/tests/voice-install.test.ts +323 -0
- package/tests/voice-platform.test.ts +176 -0
- package/tests/voice-push-to-talk.test.ts +444 -0
- package/tests/voice-session.test.ts +434 -0
- package/tests/voice-settings.test.ts +49 -0
- package/tests/voice-ui.test.ts +97 -0
- package/tests/voice-worker.test.ts +445 -0
- package/tests/weak-model-economy-prompt.test.ts +32 -0
- package/tests/windows-hidden-processes.test.ts +30 -2
- package/tests/writer-edit-surface-scope.test.ts +3 -0
- package/tests/writer-feature-document-admission.test.ts +156 -0
- package/tests/yolo-customize.test.ts +5 -1
- package/tests/yolo-mode.test.ts +19 -1
- package/tests/yolo-session-policy-capture.test.ts +104 -0
- package/tests/yolo-temp-scratch.test.ts +71 -0
- package/scripts/mirror-odd-routing.mjs +0 -242
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -4,7 +4,7 @@ import { Type } from "typebox";
|
|
|
4
4
|
import { recordReviewMutation } from "../lib/review-reminder-receipt.ts";
|
|
5
5
|
import { SESSION_CHANGE_RELAY } from "../lib/session-changes.ts";
|
|
6
6
|
import { publishForeignSessionChange } from "../lib/session-change-capture.ts";
|
|
7
|
-
import { SESSION_WORKTREE_ENTRY, SESSION_WORKTREE_CHANGED, SessionWorktreeRegistry, resolveSessionWorktree, type WorktreeResolver } from "../lib/session-worktree-registry.ts";
|
|
7
|
+
import { SESSION_WORKTREE_ENTRY, SESSION_WORKTREE_CHANGED, SessionWorktreeRegistry, nativeRootSpelling, resolveSessionWorktree, type WorktreeResolver } from "../lib/session-worktree-registry.ts";
|
|
8
8
|
import { canonicalWriterRoot } from "../lib/writer-surfaces.ts";
|
|
9
9
|
import { ForeignTargetGrants } from "../lib/foreign-target-grants.ts";
|
|
10
10
|
import { MESSAGING_REASON_MAX_UTF8_BYTES, MESSAGING_REASON_MIN_CHARACTERS, normalizeMessagingReason, SessionMessagingGrants } from "../lib/session-messaging-grants.ts";
|
|
@@ -20,21 +20,23 @@ import { JOB_GLYPH, JOB_NOTICE_TYPE, jobDetails, jobNoticeText, monitorEventsTex
|
|
|
20
20
|
import { createMonitorController, mergeMonitorBatches, type MonitorNotice } from "../lib/background-monitor.ts";
|
|
21
21
|
import { JobsView } from "../lib/jobs-view.ts";
|
|
22
22
|
import { JOBS_SIDEBAR_EVENT, JOBS_STATUS_KEY } from "../lib/jobs-sidebar-state.ts";
|
|
23
|
-
import { Text, type TUI } from "@earendil-works/pi-tui";
|
|
23
|
+
import { Text, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
24
24
|
import { invalidateSidebar } from "../lib/shell-sidebar-layout.ts";
|
|
25
|
-
import { VISUAL_SETTINGS_CHANGED } from "../lib/shell-sidebar.ts";
|
|
25
|
+
import { sidebarPart, VISUAL_SETTINGS_CHANGED } from "../lib/shell-sidebar.ts";
|
|
26
|
+
import { NativePointerRegion } from "../lib/native-pointer-region.ts";
|
|
26
27
|
import { resolveVisualSettings } from "../lib/visual-customization-policy.ts";
|
|
27
28
|
import { createCompletionQueue } from "../lib/agents-completion-delivery.ts";
|
|
28
29
|
import { createAgentMessageQueue, type PendingAgentMessage } from "../lib/agents-message-delivery.ts";
|
|
29
|
-
import { AGENT_MODE, discoverAgents, formatModelRef, loadAgentsConfig, resolveAgentProfile, withPinnedModelProfiles, type AgentDefinition, type AgentMode } from "../lib/agents-config.ts";
|
|
30
|
-
import { freezeSessionProfileAtStartup, inheritedProfileDriftNotice, resolveSessionProfile, sessionProfileRoutingAt } from "../lib/session-profile-freeze.ts";
|
|
30
|
+
import { AGENT_MODE, PROFILE_SOURCE, discoverAgents, formatModelRef, loadAgentsConfig, resolveAgentProfile, unmatchedProfileKeys, withPinnedModelProfiles, type AgentDefinition, type AgentMode, type ProfileSource } from "../lib/agents-config.ts";
|
|
31
|
+
import { freezeSessionProfileAtStartup, inheritedProfileDriftNotice, resolveSessionProfile, sessionProfileRoutingAt, type InheritedProfileOptions, type SessionProfileResolution } from "../lib/session-profile-freeze.ts";
|
|
32
|
+
import { resolveProfilePin, resolveUnversionedProjectProfile } from "../lib/agent-profile-pin.ts";
|
|
31
33
|
import { resolveBackgroundSubagentsPolicy } from "../lib/background-subagents-policy.ts";
|
|
32
34
|
import { installBackgroundCacheWarming } from "../lib/background-cache-warming.ts";
|
|
33
35
|
import { isFinished, MISSING_TOOLS_NOTE_PREFIX, TASK_EVENT, TASK_STATUS, TaskStore, type AskRequest, type TaskRecord } from "../lib/agents-protocol.ts";
|
|
34
36
|
import { AgentRunner, piCommand, abortReasonText, REQUESTED_TOOLS_ENV, type AskAnswer, type RunnerDeps, type TaskRequest } from "../lib/agents-runner.ts";
|
|
35
37
|
import { parseChildPackageInjection } from "../lib/child-package-injection.ts";
|
|
36
38
|
import { ChildMessenger, type IpcEndpoint } from "../lib/agents-messaging.ts";
|
|
37
|
-
import { ActiveSessionClient, ActiveSessionListener, SessionPresenceRegistry, type PresenceRecord, type ReceivedNotification, type SentNotification, type SessionPresenceCandidate } from "../lib/agents-session-transport.ts";
|
|
39
|
+
import { ActiveSessionClient, ActiveSessionListener, SessionPresenceError, SessionPresenceRegistry, type PresenceRecord, type ReceivedNotification, type SentNotification, type SessionPresenceCandidate } from "../lib/agents-session-transport.ts";
|
|
38
40
|
import { WindowsActiveSessionClient, WindowsActiveSessionListener, WindowsSessionPresenceRegistry, type WindowsSessionRegistryPhaseObserver } from "../lib/windows-session-transport.ts";
|
|
39
41
|
import { hasReviewSessionPermission, resolveCanonicalGitRepositoryIdentitySync, type ReviewSessionManager } from "../lib/review-session-standing-permission.ts";
|
|
40
42
|
import { inheritedUnsafeGitEnvironmentKeys } from "../lib/review-repository.ts";
|
|
@@ -49,6 +51,7 @@ import { discoverOrchestrators } from "../lib/orchestrator-discovery.ts";
|
|
|
49
51
|
import { consultPublishedMetadata, unavailableMetadata, type MetadataReceipt } from "../lib/orchestrator-consultation.ts";
|
|
50
52
|
import { HelperCostPermission } from "../lib/orchestrator-helper-consent.ts";
|
|
51
53
|
import { OrchestratorStateCache } from "../lib/orchestrator-state.ts";
|
|
54
|
+
import { TODO_PROGRESS_EVENT, type TodoProgress } from "../lib/shell-todo.ts";
|
|
52
55
|
import { normalizeTaskSubject } from "../lib/orchestrator-alias.ts";
|
|
53
56
|
import { decodeWorkDescriptor } from "../lib/orchestrator-work.ts";
|
|
54
57
|
import { validateWorkFilter, searchPublishedWork } from "../lib/orchestrator-work-search.ts";
|
|
@@ -56,8 +59,8 @@ import { OrchestratorScopeCache, type RepositoryFact } from "../lib/orchestrator
|
|
|
56
59
|
import { createRpcActivityPublisher, type RpcActivityPublisher } from "../lib/agents-rpc-publisher.ts";
|
|
57
60
|
import { isInteractiveRpcHost } from "../lib/rpc-host.ts";
|
|
58
61
|
import { createNativeFullscreenInteraction } from "../lib/native-fullscreen-interaction.ts";
|
|
59
|
-
import { AGENTS_GLYPH, renderAgentsCard, widgetExpiryMs, widgetRows } from "../lib/agents-widget.ts";
|
|
60
|
-
import { CARD_TONE, renderCard } from "../lib/shell-card.ts";
|
|
62
|
+
import { AGENTS_GLYPH, agentsCardTone, renderAgentsCard, widgetExpiryMs, widgetRows } from "../lib/agents-widget.ts";
|
|
63
|
+
import { CARD_TONE, panelHeaderRow, renderCard } from "../lib/shell-card.ts";
|
|
61
64
|
import { openInExternalEditor } from "./gentle-shell.ts";
|
|
62
65
|
import { gentlePiConfigHome } from "../lib/agent-home.ts";
|
|
63
66
|
import { resolveAgentHomeDirectory } from "../lib/agent-model-resolution.ts";
|
|
@@ -186,6 +189,44 @@ function childExtensionRequest(env: NodeJS.ProcessEnv, curated: string[] | undef
|
|
|
186
189
|
return curated && curated.length > 0 ? { extensionPaths: [...curated] } : {};
|
|
187
190
|
}
|
|
188
191
|
|
|
192
|
+
// gentle-shell#2180: a short, stable token for the profile layer that supplied
|
|
193
|
+
// the launch's model, recorded next to the model in the TaskRecord so every
|
|
194
|
+
// task card shows which layer won and stale disk edits below a frozen session
|
|
195
|
+
// become visible. Derived from the resolution the request already performed;
|
|
196
|
+
// the only profile reads are the pin/declaration lookups in the paths where
|
|
197
|
+
// `sessionProfileRoutingAt` itself reads the target's own layers without
|
|
198
|
+
// carrying the origin (a foreign target under a session frozen without a
|
|
199
|
+
// profile, and `follow`/child launches), so no layer is resolved twice to route.
|
|
200
|
+
function modelSourceToken(
|
|
201
|
+
source: ProfileSource,
|
|
202
|
+
resolution: SessionProfileResolution,
|
|
203
|
+
routingTarget: InheritedProfileOptions & { foreignRepository?: boolean },
|
|
204
|
+
): string | undefined {
|
|
205
|
+
if (source === PROFILE_SOURCE.DEFINITION) return "definition";
|
|
206
|
+
// Unresolved means no layer supplied a model at all: the card already shows
|
|
207
|
+
// `default`, and naming a layer next to it would only be noise.
|
|
208
|
+
if (source === PROFILE_SOURCE.UNRESOLVED) return undefined;
|
|
209
|
+
if (source !== PROFILE_SOURCE.PROFILE) return "default";
|
|
210
|
+
if (resolution.kind === "explicit") return "binding";
|
|
211
|
+
if (resolution.kind === "inherited") {
|
|
212
|
+
if (resolution.profile !== undefined) return `frozen:${resolution.profile.origin}`;
|
|
213
|
+
// Frozen without a profile: a foreign target keeps only its own pin
|
|
214
|
+
// layers, so a winning pin there is named from the same read
|
|
215
|
+
// `sessionProfileRoutingAt` just performed; otherwise the session keeps
|
|
216
|
+
// the materialized config routing.
|
|
217
|
+
if (routingTarget.foreignRepository === true) {
|
|
218
|
+
const pin = resolveProfilePin(routingTarget);
|
|
219
|
+
return pin === undefined ? "profile" : `pin:${pin.source}`;
|
|
220
|
+
}
|
|
221
|
+
return "profile";
|
|
222
|
+
}
|
|
223
|
+
// `follow` and live child launches read the target's own defaults
|
|
224
|
+
// (pin → declaration → global active); name the layer the same read chose.
|
|
225
|
+
const pin = resolveProfilePin(routingTarget);
|
|
226
|
+
if (pin !== undefined) return `pin:${pin.source}`;
|
|
227
|
+
return resolveUnversionedProjectProfile(routingTarget.cwd, routingTarget.configHome) !== undefined ? "declaration" : "profile";
|
|
228
|
+
}
|
|
229
|
+
|
|
189
230
|
export function agentRuntimePaths(home: string, agentHome = join(home, ".pi", "agent")): { sessions: string; transcripts: string } {
|
|
190
231
|
const root = join(agentHome, "gentle-agents");
|
|
191
232
|
return { sessions: join(root, "sessions"), transcripts: join(root, "transcripts") };
|
|
@@ -521,7 +562,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
521
562
|
};
|
|
522
563
|
return PresencePublisher.start({ profile: agentHome, sessionId, label: labelSource(), labelSource, activity: [] });
|
|
523
564
|
};
|
|
524
|
-
const
|
|
565
|
+
const publishActivityNow = (deferred = false) => {
|
|
525
566
|
if (!sessions) return;
|
|
526
567
|
try {
|
|
527
568
|
if (!presence || presence.error) {
|
|
@@ -540,14 +581,81 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
540
581
|
return data?.sessionId === activeSessionId() && typeof data.root === "string" && typeof data.evidence === "string" ? [data.root] : [];
|
|
541
582
|
});
|
|
542
583
|
presence?.updateDiscovery(transport.listener.record, { workspace: sessions.getCwd(), tasks, registered,
|
|
543
|
-
|
|
584
|
+
// A deferred (render-cadence trailing) publish must never resolve Git:
|
|
585
|
+
// its timer can land while a peer metadata query is being served, and
|
|
586
|
+
// that contract adds no caller Git probes. It reuses the snapshot the
|
|
587
|
+
// last event-time publication projected; the next event-time publish
|
|
588
|
+
// refreshes the facts.
|
|
589
|
+
scope: deferred ? scopeCache.cachedScope() : scopeCache.project(sessions.getCwd(), tasks, registered), state: stateCache.get(sessions) });
|
|
544
590
|
}
|
|
545
591
|
} catch { presence?.dispose(); presence = undefined; }
|
|
546
592
|
};
|
|
593
|
+
// Coalesced like requestRender: the first call publishes immediately so an
|
|
594
|
+
// idle→working transition or a resumed session never waits, and every call
|
|
595
|
+
// inside the window collapses into one trailing publish, so a burst of child
|
|
596
|
+
// deltas costs one publication instead of one per event.
|
|
597
|
+
let activityPublishQueued = false;
|
|
598
|
+
let activityPublishTrailing = false;
|
|
599
|
+
let cancelActivityPublish: (() => void) | undefined;
|
|
600
|
+
const resetActivityPublish = () => {
|
|
601
|
+
cancelActivityPublish?.();
|
|
602
|
+
cancelActivityPublish = undefined;
|
|
603
|
+
activityPublishQueued = false;
|
|
604
|
+
activityPublishTrailing = false;
|
|
605
|
+
};
|
|
606
|
+
const flushDeferredPublish = () => {
|
|
607
|
+
cancelActivityPublish = undefined;
|
|
608
|
+
activityPublishQueued = false;
|
|
609
|
+
if (!activityPublishTrailing) return;
|
|
610
|
+
activityPublishTrailing = false;
|
|
611
|
+
publishActivityNow(true);
|
|
612
|
+
};
|
|
613
|
+
const publishActivity = () => {
|
|
614
|
+
if (activityPublishQueued) {
|
|
615
|
+
activityPublishTrailing = true;
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
publishActivityNow();
|
|
619
|
+
activityPublishQueued = true;
|
|
620
|
+
cancelActivityPublish = deps.schedule(flushDeferredPublish, RENDER_COALESCE_MS);
|
|
621
|
+
};
|
|
622
|
+
// Discrete semantic events publish immediately so peers and readers see them
|
|
623
|
+
// promptly (a consult binds to the published snapshot); the coalescing window
|
|
624
|
+
// stays armed so a child-delta burst that follows still collapses into one
|
|
625
|
+
// trailing publish.
|
|
626
|
+
const publishActivityPrompt = () => {
|
|
627
|
+
publishActivityNow();
|
|
628
|
+
if (!activityPublishQueued) {
|
|
629
|
+
activityPublishQueued = true;
|
|
630
|
+
cancelActivityPublish = deps.schedule(flushDeferredPublish, RENDER_COALESCE_MS);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
547
633
|
const unsubscribeScope = pi.events.on(SESSION_WORKTREE_CHANGED, (event) => {
|
|
548
634
|
if ((event as { sessionId?: string } | undefined)?.sessionId !== sessions?.getSessionId()) return;
|
|
549
635
|
scopeCache.clear();
|
|
550
|
-
|
|
636
|
+
publishActivityPrompt();
|
|
637
|
+
});
|
|
638
|
+
// The todo list is the session's progress: publish it for peers so the model
|
|
639
|
+
// spends no turn on orchestrator_session_id. Merged into the owner's curated
|
|
640
|
+
// state; a publication that cannot be made never fails the todo write.
|
|
641
|
+
// Counts only: a todo title is private work text, never published to peers.
|
|
642
|
+
// The last automatic line survives an owner publication that omits `progress`.
|
|
643
|
+
let automaticProgress: { sessionId: string; text: string } | undefined;
|
|
644
|
+
const unsubscribeTodoProgress = pi.events.on(TODO_PROGRESS_EVENT, (event) => {
|
|
645
|
+
const progress = event as Partial<TodoProgress> | undefined;
|
|
646
|
+
if (!sessions || progress?.sessionId !== sessions.getSessionId() || typeof progress.done !== "number" || typeof progress.total !== "number") return;
|
|
647
|
+
const text = `${progress.done}/${progress.total} done`;
|
|
648
|
+
try {
|
|
649
|
+
const owned = stateCache.get(sessions);
|
|
650
|
+
// A state the owner withdrew stays withdrawn until the owner publishes again.
|
|
651
|
+
if (owned?.state === null) return;
|
|
652
|
+
const { progress: previous, ...curated } = owned?.state ?? {};
|
|
653
|
+
// A cleared list has no progress to report: drop the line, publish nothing new.
|
|
654
|
+
if (progress.total === 0 && previous === undefined) return;
|
|
655
|
+
stateCache.publish(sessions, progress.total === 0 ? curated : { ...curated, progress: text }, (type, data) => pi.appendEntry(type, data));
|
|
656
|
+
automaticProgress = progress.total === 0 ? undefined : { sessionId: progress.sessionId, text };
|
|
657
|
+
publishActivity();
|
|
658
|
+
} catch { /* stale session or over the state budget: keep the previous publication */ }
|
|
551
659
|
});
|
|
552
660
|
let worktrees: SessionWorktreeRegistry | undefined;
|
|
553
661
|
let worktreeManager: ExtensionContext["sessionManager"] | undefined;
|
|
@@ -594,6 +702,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
594
702
|
clearTaskMetrics();
|
|
595
703
|
unsubscribeMetrics();
|
|
596
704
|
unsubscribeScope();
|
|
705
|
+
unsubscribeTodoProgress();
|
|
597
706
|
scopeCache.clear();
|
|
598
707
|
});
|
|
599
708
|
let stopAllConfirmation: Promise<void> | undefined;
|
|
@@ -673,7 +782,11 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
673
782
|
await closeStartupTransport();
|
|
674
783
|
return;
|
|
675
784
|
}
|
|
676
|
-
|
|
785
|
+
// Publish immediately, not on the coalescing trailing edge: this is the
|
|
786
|
+
// session's first discovery publication, and a peer metadata query binds
|
|
787
|
+
// to the activation only through it — a 400 ms lag here makes a prompt
|
|
788
|
+
// consult read publication-unknown-or-stale.
|
|
789
|
+
publishActivityPrompt();
|
|
677
790
|
} catch {
|
|
678
791
|
if (activeSessionTransport?.generation === generation) activeSessionTransport = undefined;
|
|
679
792
|
await closeStartupTransport();
|
|
@@ -1502,7 +1615,11 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1502
1615
|
ctx.ui.notify(`Could not read the task's session: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
1503
1616
|
return;
|
|
1504
1617
|
}
|
|
1505
|
-
|
|
1618
|
+
try {
|
|
1619
|
+
if (!openInExternalEditor(overlayHost, transcriptPath)) ctx.ui.notify("No editor configured. Set $VISUAL or $EDITOR.", "warning");
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
1622
|
+
}
|
|
1506
1623
|
};
|
|
1507
1624
|
|
|
1508
1625
|
const writeTranscript = async (task: TaskRecord): Promise<string> => {
|
|
@@ -1534,24 +1651,80 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1534
1651
|
host?.requestRender();
|
|
1535
1652
|
});
|
|
1536
1653
|
tickClock();
|
|
1537
|
-
// Agents
|
|
1538
|
-
//
|
|
1539
|
-
// the
|
|
1654
|
+
// Agents follows Todos: the fullscreen rail shows it when the terminal is
|
|
1655
|
+
// wide enough, and sidebarPart suppresses the above-editor copy only
|
|
1656
|
+
// while the rail owns the host; narrower terminals keep the widget.
|
|
1540
1657
|
ui?.setWidget(AGENTS_WIDGET_KEY, (tui, theme) => {
|
|
1541
1658
|
host = tui;
|
|
1542
1659
|
sidebarTui = tui;
|
|
1543
|
-
return
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1660
|
+
return sidebarPart(tui, "agents", agentsCard(tui, theme, false), agentsCard(tui, theme, true));
|
|
1661
|
+
});
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
const toggleCollapsed = () => {
|
|
1665
|
+
collapsed = !collapsed;
|
|
1666
|
+
if (sidebarTui) invalidateSidebar(sidebarTui);
|
|
1667
|
+
host?.requestRender();
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
// One surface of the Agents card. The rail is natively scrollable, so it
|
|
1671
|
+
// lists every task; the above-editor widget keeps its row budget. Only the
|
|
1672
|
+
// header row is the clickable collapse control, exactly like Todos.
|
|
1673
|
+
const agentsCard = (tui: TUI, theme: Parameters<typeof renderAgentsCard>[1], rail: boolean): Component & { dispose(): void; digest(): string } => {
|
|
1674
|
+
let hovered = false;
|
|
1675
|
+
let headerRow = 0;
|
|
1676
|
+
const card: Component = {
|
|
1677
|
+
render(width: number) {
|
|
1678
|
+
if (!agentsVisible) return [];
|
|
1679
|
+
const tasks = visibleTasks();
|
|
1680
|
+
const now = deps.now();
|
|
1681
|
+
headerRow = panelHeaderRow(theme, width, agentsCardTone(tasks, now));
|
|
1682
|
+
const lines = renderAgentsCard(tasks, theme, width, now, { collapsed, collapseKey, maxRows: rail ? undefined : widgetRows(tui.terminal?.rows), viewKey, collapseControl: true, hovered });
|
|
1683
|
+
return lines.length === 0 || rail ? lines : [...lines, ""];
|
|
1684
|
+
},
|
|
1685
|
+
invalidate() {
|
|
1686
|
+
hovered = false;
|
|
1687
|
+
},
|
|
1688
|
+
};
|
|
1689
|
+
const region = new NativePointerRegion(card, {
|
|
1690
|
+
onHover(event) {
|
|
1691
|
+
const next = event.y === headerRow;
|
|
1692
|
+
if (next === hovered) return { handled: true };
|
|
1693
|
+
hovered = next;
|
|
1694
|
+
return { handled: true, render: true };
|
|
1695
|
+
},
|
|
1696
|
+
onLeave() {
|
|
1697
|
+
if (!hovered) return;
|
|
1698
|
+
hovered = false;
|
|
1699
|
+
host?.requestRender();
|
|
1700
|
+
},
|
|
1701
|
+
onClick(event) {
|
|
1702
|
+
if (event.button !== "left" || event.y !== headerRow) return undefined;
|
|
1703
|
+
toggleCollapsed();
|
|
1704
|
+
return { handled: true, render: true };
|
|
1705
|
+
},
|
|
1551
1706
|
});
|
|
1707
|
+
return {
|
|
1708
|
+
// The rail memo repaints a part only when its digest moves: task
|
|
1709
|
+
// state, the local controls, and the elapsed clock (to the second)
|
|
1710
|
+
// only while a task is still running, so an idle or finished card
|
|
1711
|
+
// never forces a rail rebuild every second.
|
|
1712
|
+
digest: () => {
|
|
1713
|
+
const tasks = visibleTasks();
|
|
1714
|
+
const clock = tasks.some((task) => !isFinished(task.status)) ? Math.floor(deps.now() / 1000) : 0;
|
|
1715
|
+
return JSON.stringify([agentsVisible, collapsed, hovered, tui.terminal?.rows, clock, tasks.map((task) => [task.id, task.status, task.tokens, task.cost, task.label, task.lastStep, task.error])]);
|
|
1716
|
+
},
|
|
1717
|
+
render: (width) => region.render(width),
|
|
1718
|
+
handleMouse: (event) => region.handleMouse(event),
|
|
1719
|
+
invalidate: () => region.invalidate(),
|
|
1720
|
+
dispose: () => region.dispose(),
|
|
1721
|
+
};
|
|
1552
1722
|
};
|
|
1553
1723
|
|
|
1554
1724
|
const roots = (ctx: ExtensionContext) => ({ cwd: ctx.sessionManager.getCwd(), home: deps.home, agentHome });
|
|
1725
|
+
// Issue #2400: parent sessions already told about model_profiles keys that
|
|
1726
|
+
// match no agent and suggest none, so the notice fires once per session.
|
|
1727
|
+
const unmatchedKeysNotifiedSessions = new Set<string>();
|
|
1555
1728
|
|
|
1556
1729
|
const buildRequest = async (ctx: ExtensionContext, agent: AgentDefinition, prompt: string, label: string | undefined, context: string | undefined, mode: AgentMode, resume?: string, workspaceRoot?: string, signal?: AbortSignal, repositoryRoot?: string): Promise<TaskRequest> => {
|
|
1557
1730
|
if (retiredSddAgent(agent.name)) throw new Error("Retired SDD agents cannot be dispatched.");
|
|
@@ -1615,7 +1788,8 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1615
1788
|
// same-clone registry. Never use a caller-injected resolver for this identity.
|
|
1616
1789
|
const identity = resolveSessionWorktree(selectedRoot, parentCwd);
|
|
1617
1790
|
const canonicalParent = resolveSessionWorktree(parentCwd, parentCwd);
|
|
1618
|
-
|
|
1791
|
+
const spelling = nativeRootSpelling(selectedRoot);
|
|
1792
|
+
if (!identity || (canonicalParent && identity.commonDir === canonicalParent.commonDir) || spelling !== identity.root || !isAbsolute(spelling) || realpathSync(spelling) !== spelling) throw new Error("repository_root must select a canonical independent Git repository.");
|
|
1619
1793
|
await foreignGrants.authorize(ctx, identity, { continuation: resume !== undefined, signal });
|
|
1620
1794
|
foreignIdentity = identity;
|
|
1621
1795
|
foreignParent = canonicalParent;
|
|
@@ -1641,14 +1815,17 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1641
1815
|
// included; only `follow` reads the defaults of the target. The profile is
|
|
1642
1816
|
// resolved here, at task-request creation, so queued and running children
|
|
1643
1817
|
// keep the routing frozen into their requests even if the session rebinds.
|
|
1818
|
+
// The routing target this launch reads (#1064), reused to name the winning
|
|
1819
|
+
// layer (#2180) so both answers come from one resolution.
|
|
1820
|
+
const routingTarget = {
|
|
1821
|
+
cwd: target ?? parentCwd,
|
|
1822
|
+
configHome: profileConfigHome,
|
|
1823
|
+
resolveWorktree: pinIdentity,
|
|
1824
|
+
foreignRepository: foreign,
|
|
1825
|
+
};
|
|
1644
1826
|
const config = withPinnedModelProfiles(
|
|
1645
1827
|
loadAgentsConfig(roots(ctx)),
|
|
1646
|
-
sessionProfileRoutingAt(sessionProfile,
|
|
1647
|
-
cwd: target ?? parentCwd,
|
|
1648
|
-
configHome: profileConfigHome,
|
|
1649
|
-
resolveWorktree: pinIdentity,
|
|
1650
|
-
foreignRepository: foreign,
|
|
1651
|
-
}),
|
|
1828
|
+
sessionProfileRoutingAt(sessionProfile, routingTarget),
|
|
1652
1829
|
);
|
|
1653
1830
|
// Drift is measured against the defaults of the session's own directory,
|
|
1654
1831
|
// where the profile was frozen, never against a delegated target.
|
|
@@ -1663,6 +1840,31 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1663
1840
|
if (drift !== undefined) ctx.ui.notify(drift, "info");
|
|
1664
1841
|
}
|
|
1665
1842
|
const profile = resolveAgentProfile(agent, config);
|
|
1843
|
+
// Issue #2196 (PR #2233 maintainer decision): a model_profiles key that
|
|
1844
|
+
// names no discovered agent was silently ignored while the child inherited
|
|
1845
|
+
// the orchestrator model. When a configured key suggests this agent, name
|
|
1846
|
+
// it at launch; resolution stays exact-match, so an omitted role keeps
|
|
1847
|
+
// inheriting and a matched key never warns. Keys are checked against every
|
|
1848
|
+
// discovered agent, so a key naming another agent is never reported here.
|
|
1849
|
+
if (ctx.hasUI) {
|
|
1850
|
+
const unmatched = unmatchedProfileKeys(config.modelProfiles, discoverAgents(roots(ctx)).agents);
|
|
1851
|
+
const nearMisses = unmatched.filter((entry) => entry.suggestion === agent.name);
|
|
1852
|
+
if (nearMisses.length > 0) {
|
|
1853
|
+
ctx.ui.notify(nearMisses.map((entry) => `"${entry.key}" did not match any agent, did you mean ${agent.name}?`).join(" "), "warning");
|
|
1854
|
+
}
|
|
1855
|
+
// Issue #2400: a key with no suggestion never names the launched agent,
|
|
1856
|
+
// so the filter above kept it silent. The session's first launch lists
|
|
1857
|
+
// every such key once; later launches in the same session stay quiet.
|
|
1858
|
+
const unsuggested = unmatched.filter((entry) => entry.suggestion === undefined);
|
|
1859
|
+
const parentSession = ctx.sessionManager.getSessionId() ?? "";
|
|
1860
|
+
if (unsuggested.length > 0 && !unmatchedKeysNotifiedSessions.has(parentSession)) {
|
|
1861
|
+
unmatchedKeysNotifiedSessions.add(parentSession);
|
|
1862
|
+
ctx.ui.notify(unsuggested.map((entry) => `"${sanitizeTerminalText(entry.key)}" did not match any agent and was ignored.`).join(" "), "warning");
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
// #2180: the winning layer is named from the resolution and routing this
|
|
1866
|
+
// request already performed, never by re-resolving them.
|
|
1867
|
+
const modelSource = modelSourceToken(profile.source.model, sessionProfile, routingTarget);
|
|
1666
1868
|
if (admittedModel !== undefined) {
|
|
1667
1869
|
const model = profile.model ?? ctx.model;
|
|
1668
1870
|
const catalogModel = model?.provider ? ctx.modelRegistry?.find(model.provider, model.id) : ctx.modelRegistry?.getAll().find(candidate => candidate.id === model?.id);
|
|
@@ -1703,6 +1905,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1703
1905
|
} } : {}),
|
|
1704
1906
|
...(target === undefined || foreign ? {} : { onLaunch: () => { registry.register(target, "subagent:spawn"); } }),
|
|
1705
1907
|
model: profile.model,
|
|
1908
|
+
...(modelSource === undefined ? {} : { modelSource }),
|
|
1706
1909
|
thinking: profile.thinking,
|
|
1707
1910
|
sessionDir,
|
|
1708
1911
|
resumeSessionPath: resume,
|
|
@@ -1769,7 +1972,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1769
1972
|
if (launched && foreignRequest) foreignTasks.set(task.id, foreignRequest);
|
|
1770
1973
|
ownedTaskIds.add(task.id);
|
|
1771
1974
|
publishWork?.(task.id);
|
|
1772
|
-
|
|
1975
|
+
publishActivityPrompt(); // Admission's summary notification precedes runtime ownership.
|
|
1773
1976
|
store.subscribe(task.id, () => { publishActivity(); requestRender(); });
|
|
1774
1977
|
if (request.mode === AGENT_MODE.BACKGROUND) return text(`Started ${task.agent} in the background as task ${task.id}. Retain that id; completion is pushed automatically. Never sleep or periodically poll subagent_status/subagent_result for completion or cache maintenance. Inspect status only at a real orchestration decision boundary; never relaunch equivalent queued/running work.`, taskDetails(task));
|
|
1775
1978
|
// A tool call aborted by the host (a human interrupting the turn, a timeout)
|
|
@@ -1842,7 +2045,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1842
2045
|
pi.registerTool({
|
|
1843
2046
|
name: "orchestrator_session_id",
|
|
1844
2047
|
label: "Orchestrator session ID",
|
|
1845
|
-
description: "Return this host session's stable routing ID, Initial alias (FIRST TASK), Current alias (CURRENT TASK), and separate human Session name. Explicit subjects update Current even when named; Initial stays the first declared branch topic, unknown for legacy history. Before delegation or cross-session coordination, declare a short recognizable subject here. Do not require a subject declaration for small direct tasks; do not query all peers. Names never authenticate. Existing Pi names and human renames are preserved. Use a concise non-sensitive label, not a prompt.
|
|
2048
|
+
description: "Return this host session's stable routing ID, Initial alias (FIRST TASK), Current alias (CURRENT TASK), and separate human Session name. Explicit subjects update Current even when named; Initial stays the first declared branch topic, unknown for legacy history. Publishing state.work with an explicit topic and no subject also refreshes Current from that topic; keep work.topic current when the task changes. Before delegation or cross-session coordination, declare a short recognizable subject here. Do not require a subject declaration for small direct tasks; do not query all peers. Names never authenticate. Existing Pi names and human renames are preserved. Use a concise non-sensitive label, not a prompt. Progress is published automatically from the todo list; publish state here only for objective, decisions, blockers, or work, never just to report progress. Owner-curated state (2048 UTF-8 bytes total): null withdraws, omission leaves unchanged. Never include credentials, internal instructions, or raw prompts. Historical notes are not consent or an owner reply.",
|
|
1846
2049
|
...orchestratorToolRenderers("session", expandHint),
|
|
1847
2050
|
parameters: { type: "object", additionalProperties: false, properties: {
|
|
1848
2051
|
subject: { type: "string", maxLength: 120, description: "Optional short task subject; updates task aliases independently of human names. Also names an unnamed Pi session." },
|
|
@@ -1864,12 +2067,25 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1864
2067
|
if (!transport) return text("Error: session messaging is not ready.", { error: "not ready" });
|
|
1865
2068
|
const { subject, state } = params as { subject?: unknown; state?: unknown };
|
|
1866
2069
|
const declared = normalizeTaskSubject(subject);
|
|
1867
|
-
|
|
2070
|
+
// An owner object without `progress` keeps the automatic todo progress still on
|
|
2071
|
+
// display; null and an explicit `progress` win, and an over-budget merge does not fail the call.
|
|
2072
|
+
const shown = stateCache.get(ctx.sessionManager)?.state?.progress;
|
|
2073
|
+
const merged = state && typeof state === "object" && !Array.isArray(state) && !Object.hasOwn(state, "progress")
|
|
2074
|
+
&& automaticProgress?.sessionId === ctx.sessionManager.getSessionId() && shown === automaticProgress.text
|
|
2075
|
+
? { ...state, progress: shown } : undefined;
|
|
2076
|
+
const append = (type: string, data: unknown) => pi.appendEntry(type, data);
|
|
2077
|
+
if (merged) {
|
|
2078
|
+
try { stateCache.publish(ctx.sessionManager, merged, append, Date.now(), declared); }
|
|
2079
|
+
catch (error) {
|
|
2080
|
+
if ((error as Error).message !== "invalid-published-state") throw error;
|
|
2081
|
+
stateCache.publish(ctx.sessionManager, state, append, Date.now(), declared);
|
|
2082
|
+
}
|
|
2083
|
+
} else if (state !== undefined || declared) stateCache.publish(ctx.sessionManager, state, append, Date.now(), declared);
|
|
1868
2084
|
if (activeTransportFor(ctx) !== transport) throw new Error("stale-published-state");
|
|
1869
2085
|
if (declared && !ctx.sessionManager.getSessionName?.()) pi.setSessionName(declared);
|
|
1870
2086
|
const alias = sanitizeDisplayLabel(ctx.sessionManager.getSessionName?.() ?? "");
|
|
1871
2087
|
const aliases = stateCache.get(ctx.sessionManager)?.aliases;
|
|
1872
|
-
if (state !== undefined || declared)
|
|
2088
|
+
if (state !== undefined || declared) publishActivityPrompt();
|
|
1873
2089
|
presence?.refreshLabel();
|
|
1874
2090
|
return text(`Active session ID: ${transport.sessionId}\nCurrent alias (CURRENT TASK): ${aliases?.currentAlias ?? "unknown"}\nInitial alias (FIRST TASK): ${aliases?.initialAlias ?? "unknown"}\nSession name: ${alias || "unnamed"}`,
|
|
1875
2091
|
{ gentleAgents: { senderSessionId: transport.sessionId, alias, sessionName: alias, initialAlias: aliases?.initialAlias ?? null, currentAlias: aliases?.currentAlias ?? null } });
|
|
@@ -1977,17 +2193,24 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1977
2193
|
? `repository: ${fact.root} · clone: ${fact.cloneHash} · Git resolved at: ${fact.resolvedAt} (${fact.source})`
|
|
1978
2194
|
: "repository: unknown";
|
|
1979
2195
|
const rows = peers.map(peer => {
|
|
1980
|
-
const context = peer.freshness === "recent" ? ` · ${peer.aliases?.currentAlias || peer.label || "unnamed"} · Session name: ${peer.label || "unnamed"} · Current alias (CURRENT TASK): ${peer.aliases?.currentAlias ?? "unknown"} · Initial alias (FIRST TASK): ${peer.aliases?.initialAlias ?? "unknown"} · recorded workspace: ${peer.workspace || "unknown"}` :
|
|
2196
|
+
const context = peer.freshness === "recent" ? ` · ${peer.aliases?.currentAlias || peer.label || "unnamed"} · Session name: ${peer.label || "unnamed"} · Current alias (CURRENT TASK): ${peer.aliases?.currentAlias ?? "unknown"} · Initial alias (FIRST TASK): ${peer.aliases?.initialAlias ?? "unknown"} · recorded workspace: ${peer.workspace || "unknown"}` : peer.legacyLabel !== undefined
|
|
2197
|
+
? ` · ${peer.legacyLabel || "unnamed"} · context: legacy (this session predates discovery publication; restart it to publish its workspace and repository)`
|
|
2198
|
+
: " · context: unknown";
|
|
1981
2199
|
const tasks = peer.tasks?.map(task => `\n - ${task.label || task.id} [${task.status}] · launch workspace: ${task.workspace || "unknown"} · ${repository(peer.scope?.tasks.find(t => t.id === task.id)?.repository)}`).join("") ?? "";
|
|
1982
2200
|
const registered = peer.scope?.registered.map(fact => `\n registered: ${repository(fact)}`).join("") ?? "";
|
|
1983
2201
|
const gaps = peer.scope && !peer.scope.complete ? `\n (scope incomplete: ${peer.scope.omittedTasks} tasks, ${peer.scope.omittedRegistered} registered roots omitted)` : "";
|
|
1984
2202
|
const catalog = peer.catalog ? `\n recorded catalog (not Git identity): ${JSON.stringify(peer.catalog)}` : "\n recorded catalog: unknown";
|
|
1985
2203
|
const note = params.recipient_session_id ? `\n owner-curated recorded state (not consent or owner reply; authority none): ${peer.state ? JSON.stringify(peer.state) : "unknown"}` : "";
|
|
1986
|
-
|
|
2204
|
+
// A legacy peer never publishes its scope: say so instead of reporting an unknown.
|
|
2205
|
+
const legacy = peer.legacyLabel !== undefined;
|
|
2206
|
+
return `- ${peer.sessionId}${context} · ${legacy ? "repository: not published" : repository(peer.scope?.host)} · metadata: ${legacy ? "legacy" : peer.freshness}${tasks}${registered}${gaps}${catalog}${note}${peer.omitted ? `\n (${peer.omitted} more tasks omitted)` : ""}`;
|
|
1987
2207
|
});
|
|
1988
2208
|
return peers.length === 0 ? text("No other sessions are currently advertised. Advertisements have unknown reachability and do not guarantee a live session.") : text(`Advertised sessions (reachability is unknown):\n${rows.join("\n")}`, { gentleAgents: { candidates: peers } });
|
|
1989
|
-
} catch {
|
|
1990
|
-
|
|
2209
|
+
} catch (error) {
|
|
2210
|
+
// SessionPresenceError codes are a fixed enum with no record content,
|
|
2211
|
+
// so surfacing the reason is safe and keeps diagnosis possible.
|
|
2212
|
+
const reason = error instanceof SessionPresenceError ? error.code : "unknown";
|
|
2213
|
+
return text(`Error: session discovery is unavailable (${reason}).`, { error: "unavailable", reason });
|
|
1991
2214
|
}
|
|
1992
2215
|
},
|
|
1993
2216
|
});
|
|
@@ -2045,8 +2268,9 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2045
2268
|
recipient = candidates[index].sessionId;
|
|
2046
2269
|
activation = candidates[index];
|
|
2047
2270
|
}
|
|
2048
|
-
} catch {
|
|
2049
|
-
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
const reason = error instanceof SessionPresenceError ? error.code : "unknown";
|
|
2273
|
+
return text(`Error: session discovery is unavailable (${reason}).`, { error: "unavailable", reason });
|
|
2050
2274
|
}
|
|
2051
2275
|
}
|
|
2052
2276
|
if (recipient === undefined || !validTransportSessionId(recipient)) return text("Error: recipient session ID or message is invalid.", { error: "invalid input" });
|
|
@@ -2196,11 +2420,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2196
2420
|
if (collapseKey) {
|
|
2197
2421
|
pi.registerShortcut(collapseKey as Parameters<ExtensionAPI["registerShortcut"]>[0], {
|
|
2198
2422
|
description: "Collapse or expand the agents card",
|
|
2199
|
-
handler: async () =>
|
|
2200
|
-
collapsed = !collapsed;
|
|
2201
|
-
if (sidebarTui) invalidateSidebar(sidebarTui);
|
|
2202
|
-
host?.requestRender();
|
|
2203
|
-
},
|
|
2423
|
+
handler: async () => toggleCollapsed(),
|
|
2204
2424
|
});
|
|
2205
2425
|
}
|
|
2206
2426
|
|
|
@@ -2256,7 +2476,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2256
2476
|
helperPermission.clear();
|
|
2257
2477
|
if (sessions !== ctx.sessionManager) return;
|
|
2258
2478
|
stateCache.load(ctx.sessionManager);
|
|
2259
|
-
|
|
2479
|
+
publishActivityPrompt();
|
|
2260
2480
|
});
|
|
2261
2481
|
pi.on("session_start", async (event, ctx) => {
|
|
2262
2482
|
// gentle-shell#1064 slice 3b-ii: a parent session that has no explicit
|
|
@@ -2298,7 +2518,8 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2298
2518
|
if (preexisting && sessionId) void restoreSessionHistory(ctx, sessionId);
|
|
2299
2519
|
try {
|
|
2300
2520
|
presence = startPresence(ctx.sessionManager);
|
|
2301
|
-
|
|
2521
|
+
resetActivityPublish();
|
|
2522
|
+
publishActivityPrompt();
|
|
2302
2523
|
} catch { presence = undefined; }
|
|
2303
2524
|
void startSessionTransport(ctx);
|
|
2304
2525
|
// The desktop app's own pi process: publish live subagent state through
|
|
@@ -2341,6 +2562,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2341
2562
|
rpcActivityPublisher?.stop();
|
|
2342
2563
|
rpcActivityPublisher = undefined;
|
|
2343
2564
|
cancelClock?.();
|
|
2565
|
+
resetActivityPublish();
|
|
2344
2566
|
for (const view of overlays) { view.handleInput("q"); view.dispose(); }
|
|
2345
2567
|
overlays.clear();
|
|
2346
2568
|
sessions = undefined;
|