gentle-pi 4.3.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 +8 -5
- package/assets/agents/gentle-ai-worker.md +1 -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 +2 -2
- package/assets/orchestrator-verification.md +1 -1
- package/assets/orchestrator.md +2 -2
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +101 -151
- package/docs/bundled-install.md +47 -42
- package/docs/delegated-verification.md +6 -0
- package/docs/gentle-agents-activity.md +5 -1
- package/docs/gentle-music.md +61 -0
- package/docs/gentle-shell.md +77 -7
- package/docs/install-wizard.md +30 -6
- package/docs/permission-modes.md +7 -2
- package/docs/readme-reference.md +19 -15
- package/docs/session-profile-format.md +26 -0
- package/docs/voice.md +259 -0
- package/docs/yolo-mode.md +17 -2
- package/extensions/ask-user-choice.ts +4 -1
- package/extensions/gentle-agents.ts +173 -38
- package/extensions/gentle-ai.ts +214 -69
- package/extensions/gentle-design.ts +158 -0
- package/extensions/gentle-music.ts +289 -0
- package/extensions/gentle-shell.ts +332 -73
- package/extensions/gentle-voice.ts +11 -0
- package/extensions/session-scratch.ts +41 -22
- package/extensions/skill-registry.ts +63 -8
- package/lib/agent-profiles.ts +26 -0
- package/lib/agents-config.ts +35 -0
- package/lib/agents-protocol.ts +57 -1
- package/lib/agents-runner.ts +52 -3
- package/lib/agents-session-transport.ts +101 -6
- 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/clipboard-image.ts +147 -0
- package/lib/command-palette-catalog.ts +2 -0
- package/lib/commit-slice-signal.ts +1 -1
- package/lib/context-backstop.ts +36 -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/external-editor.ts +31 -0
- package/lib/gentle-design.ts +513 -0
- package/lib/gentle-shell-bundled.ts +123 -49
- package/lib/gentle-shell-launcher.ts +9 -4
- package/lib/gentle-shell-startup-update.ts +61 -9
- package/lib/gentle-shell-uninstall.ts +33 -1
- 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 +11 -4
- 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-presence.ts +78 -8
- package/lib/orchestrator-scope.ts +10 -0
- package/lib/orchestrator-state.ts +11 -3
- package/lib/permission-mode-policy.ts +2 -2
- package/lib/review-host-relay.ts +11 -2
- package/lib/review-reminder-receipt.ts +24 -1
- package/lib/session-profile-append-controller.ts +713 -0
- package/lib/session-profile-disk-reader.ts +182 -0
- package/lib/session-profile-persistence.ts +15 -7
- package/lib/session-scratch.ts +52 -3
- package/lib/session-worktree-registry.ts +7 -0
- package/lib/shell-bar.ts +4 -3
- package/lib/shell-changes.ts +81 -5
- package/lib/shell-prompt.ts +4 -4
- package/lib/shell-sidebar-layout.ts +26 -6
- package/lib/shell-todo.ts +1 -1
- package/lib/verifier-admission.ts +5 -4
- 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/package.json +1 -1
- package/runtime/gentle-shell-bundled.mjs +122 -48
- package/runtime/gentle-shell-launcher.mjs +9 -4
- package/runtime/gentle-shell-startup-update.mjs +61 -9
- package/runtime/gentle-shell-uninstall.mjs +33 -1
- package/runtime/native-review-cli.mjs +11 -4
- package/scripts/bundled-engram-migration.mjs +0 -6
- 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-windows.mjs +37 -7
- package/scripts/main-channel.mjs +91 -17
- package/scripts/pi-update.mjs +34 -6
- package/scripts/verify-package-files.mjs +26 -2
- package/scripts/voice-worker.cjs +476 -0
- package/skills/design/SKILL.md +110 -0
- package/tests/agent-profiles.test.ts +24 -0
- 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 +226 -5
- 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 +20 -0
- package/tests/bundled-install.test.ts +3 -3
- package/tests/clipboard-image.test.ts +114 -0
- package/tests/command-palette.test.ts +1 -1
- package/tests/context-backstop.test.ts +70 -4
- package/tests/delegate-for-reason-contract.test.ts +2 -1
- 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/engram-safety.test.ts +132 -57
- package/tests/external-editor.test.ts +84 -0
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +3 -3
- package/tests/gentle-agents.test.ts +365 -20
- package/tests/gentle-ai-binary.test.ts +1 -1
- 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-bundled.test.ts +301 -248
- package/tests/gentle-shell-launcher.test.ts +28 -2
- package/tests/gentle-shell-startup-update.test.ts +77 -1
- package/tests/gentle-shell-uninstall.test.ts +59 -0
- package/tests/gentle-shell.test.ts +183 -8
- package/tests/gentle-todo.test.ts +11 -0
- 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 +2 -2
- package/tests/installer-windows-bootstrap.test.ts +61 -3
- 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 +11 -4
- package/tests/native-review-parity.test.ts +1 -1
- package/tests/notification-audio-native.test.ts +128 -9
- package/tests/notification-customize.test.ts +17 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +2 -1
- package/tests/orchestrator-budget.test.ts +5 -2
- package/tests/orchestrator-presence.test.ts +100 -2
- package/tests/orchestrator-rdd-ownership.test.ts +2 -2
- package/tests/orchestrator-scope.test.ts +14 -0
- package/tests/orchestrator-state.test.ts +29 -0
- package/tests/package-files.test.ts +34 -0
- package/tests/package-manifest.test.ts +6 -6
- package/tests/permission-mode-policy.test.ts +10 -1
- package/tests/pi-update.test.ts +99 -4
- package/tests/rdd-aware-verification-contract.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +88 -1
- 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 +23 -2
- package/tests/review-relay-transport-agent.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +31 -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-scratch.test.ts +68 -2
- package/tests/session-worktree-registry.test.ts +13 -1
- package/tests/shell-bar.test.ts +14 -2
- package/tests/shell-changes.test.ts +148 -1
- 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/task-size-routing-contract.test.ts +1 -1
- package/tests/test-strength-contract.test.ts +1 -1
- package/tests/verifier-admission.test.ts +18 -1
- 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 +25 -2
- package/tests/yolo-customize.test.ts +5 -1
- package/tests/yolo-mode.test.ts +1 -1
- package/tests/yolo-session-policy-capture.test.ts +104 -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,13 +20,14 @@ 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, PROFILE_SOURCE, discoverAgents, formatModelRef, loadAgentsConfig, resolveAgentProfile, withPinnedModelProfiles, type AgentDefinition, type AgentMode, type ProfileSource } from "../lib/agents-config.ts";
|
|
30
|
+
import { AGENT_MODE, PROFILE_SOURCE, discoverAgents, formatModelRef, loadAgentsConfig, resolveAgentProfile, unmatchedProfileKeys, withPinnedModelProfiles, type AgentDefinition, type AgentMode, type ProfileSource } from "../lib/agents-config.ts";
|
|
30
31
|
import { freezeSessionProfileAtStartup, inheritedProfileDriftNotice, resolveSessionProfile, sessionProfileRoutingAt, type InheritedProfileOptions, type SessionProfileResolution } from "../lib/session-profile-freeze.ts";
|
|
31
32
|
import { resolveProfilePin, resolveUnversionedProjectProfile } from "../lib/agent-profile-pin.ts";
|
|
32
33
|
import { resolveBackgroundSubagentsPolicy } from "../lib/background-subagents-policy.ts";
|
|
@@ -35,7 +36,7 @@ import { isFinished, MISSING_TOOLS_NOTE_PREFIX, TASK_EVENT, TASK_STATUS, TaskSto
|
|
|
35
36
|
import { AgentRunner, piCommand, abortReasonText, REQUESTED_TOOLS_ENV, type AskAnswer, type RunnerDeps, type TaskRequest } from "../lib/agents-runner.ts";
|
|
36
37
|
import { parseChildPackageInjection } from "../lib/child-package-injection.ts";
|
|
37
38
|
import { ChildMessenger, type IpcEndpoint } from "../lib/agents-messaging.ts";
|
|
38
|
-
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";
|
|
39
40
|
import { WindowsActiveSessionClient, WindowsActiveSessionListener, WindowsSessionPresenceRegistry, type WindowsSessionRegistryPhaseObserver } from "../lib/windows-session-transport.ts";
|
|
40
41
|
import { hasReviewSessionPermission, resolveCanonicalGitRepositoryIdentitySync, type ReviewSessionManager } from "../lib/review-session-standing-permission.ts";
|
|
41
42
|
import { inheritedUnsafeGitEnvironmentKeys } from "../lib/review-repository.ts";
|
|
@@ -58,8 +59,8 @@ import { OrchestratorScopeCache, type RepositoryFact } from "../lib/orchestrator
|
|
|
58
59
|
import { createRpcActivityPublisher, type RpcActivityPublisher } from "../lib/agents-rpc-publisher.ts";
|
|
59
60
|
import { isInteractiveRpcHost } from "../lib/rpc-host.ts";
|
|
60
61
|
import { createNativeFullscreenInteraction } from "../lib/native-fullscreen-interaction.ts";
|
|
61
|
-
import { AGENTS_GLYPH, renderAgentsCard, widgetExpiryMs, widgetRows } from "../lib/agents-widget.ts";
|
|
62
|
-
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";
|
|
63
64
|
import { openInExternalEditor } from "./gentle-shell.ts";
|
|
64
65
|
import { gentlePiConfigHome } from "../lib/agent-home.ts";
|
|
65
66
|
import { resolveAgentHomeDirectory } from "../lib/agent-model-resolution.ts";
|
|
@@ -561,7 +562,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
561
562
|
};
|
|
562
563
|
return PresencePublisher.start({ profile: agentHome, sessionId, label: labelSource(), labelSource, activity: [] });
|
|
563
564
|
};
|
|
564
|
-
const
|
|
565
|
+
const publishActivityNow = (deferred = false) => {
|
|
565
566
|
if (!sessions) return;
|
|
566
567
|
try {
|
|
567
568
|
if (!presence || presence.error) {
|
|
@@ -580,14 +581,59 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
580
581
|
return data?.sessionId === activeSessionId() && typeof data.root === "string" && typeof data.evidence === "string" ? [data.root] : [];
|
|
581
582
|
});
|
|
582
583
|
presence?.updateDiscovery(transport.listener.record, { workspace: sessions.getCwd(), tasks, registered,
|
|
583
|
-
|
|
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) });
|
|
584
590
|
}
|
|
585
591
|
} catch { presence?.dispose(); presence = undefined; }
|
|
586
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
|
+
};
|
|
587
633
|
const unsubscribeScope = pi.events.on(SESSION_WORKTREE_CHANGED, (event) => {
|
|
588
634
|
if ((event as { sessionId?: string } | undefined)?.sessionId !== sessions?.getSessionId()) return;
|
|
589
635
|
scopeCache.clear();
|
|
590
|
-
|
|
636
|
+
publishActivityPrompt();
|
|
591
637
|
});
|
|
592
638
|
// The todo list is the session's progress: publish it for peers so the model
|
|
593
639
|
// spends no turn on orchestrator_session_id. Merged into the owner's curated
|
|
@@ -736,7 +782,11 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
736
782
|
await closeStartupTransport();
|
|
737
783
|
return;
|
|
738
784
|
}
|
|
739
|
-
|
|
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();
|
|
740
790
|
} catch {
|
|
741
791
|
if (activeSessionTransport?.generation === generation) activeSessionTransport = undefined;
|
|
742
792
|
await closeStartupTransport();
|
|
@@ -1565,7 +1615,11 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1565
1615
|
ctx.ui.notify(`Could not read the task's session: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
1566
1616
|
return;
|
|
1567
1617
|
}
|
|
1568
|
-
|
|
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
|
+
}
|
|
1569
1623
|
};
|
|
1570
1624
|
|
|
1571
1625
|
const writeTranscript = async (task: TaskRecord): Promise<string> => {
|
|
@@ -1597,24 +1651,80 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1597
1651
|
host?.requestRender();
|
|
1598
1652
|
});
|
|
1599
1653
|
tickClock();
|
|
1600
|
-
// Agents
|
|
1601
|
-
//
|
|
1602
|
-
// 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.
|
|
1603
1657
|
ui?.setWidget(AGENTS_WIDGET_KEY, (tui, theme) => {
|
|
1604
1658
|
host = tui;
|
|
1605
1659
|
sidebarTui = tui;
|
|
1606
|
-
return
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
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
|
+
},
|
|
1614
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
|
+
};
|
|
1615
1722
|
};
|
|
1616
1723
|
|
|
1617
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>();
|
|
1618
1728
|
|
|
1619
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> => {
|
|
1620
1730
|
if (retiredSddAgent(agent.name)) throw new Error("Retired SDD agents cannot be dispatched.");
|
|
@@ -1678,7 +1788,8 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1678
1788
|
// same-clone registry. Never use a caller-injected resolver for this identity.
|
|
1679
1789
|
const identity = resolveSessionWorktree(selectedRoot, parentCwd);
|
|
1680
1790
|
const canonicalParent = resolveSessionWorktree(parentCwd, parentCwd);
|
|
1681
|
-
|
|
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.");
|
|
1682
1793
|
await foreignGrants.authorize(ctx, identity, { continuation: resume !== undefined, signal });
|
|
1683
1794
|
foreignIdentity = identity;
|
|
1684
1795
|
foreignParent = canonicalParent;
|
|
@@ -1729,6 +1840,28 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1729
1840
|
if (drift !== undefined) ctx.ui.notify(drift, "info");
|
|
1730
1841
|
}
|
|
1731
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
|
+
}
|
|
1732
1865
|
// #2180: the winning layer is named from the resolution and routing this
|
|
1733
1866
|
// request already performed, never by re-resolving them.
|
|
1734
1867
|
const modelSource = modelSourceToken(profile.source.model, sessionProfile, routingTarget);
|
|
@@ -1839,7 +1972,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1839
1972
|
if (launched && foreignRequest) foreignTasks.set(task.id, foreignRequest);
|
|
1840
1973
|
ownedTaskIds.add(task.id);
|
|
1841
1974
|
publishWork?.(task.id);
|
|
1842
|
-
|
|
1975
|
+
publishActivityPrompt(); // Admission's summary notification precedes runtime ownership.
|
|
1843
1976
|
store.subscribe(task.id, () => { publishActivity(); requestRender(); });
|
|
1844
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));
|
|
1845
1978
|
// A tool call aborted by the host (a human interrupting the turn, a timeout)
|
|
@@ -1912,7 +2045,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1912
2045
|
pi.registerTool({
|
|
1913
2046
|
name: "orchestrator_session_id",
|
|
1914
2047
|
label: "Orchestrator session ID",
|
|
1915
|
-
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. 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.",
|
|
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.",
|
|
1916
2049
|
...orchestratorToolRenderers("session", expandHint),
|
|
1917
2050
|
parameters: { type: "object", additionalProperties: false, properties: {
|
|
1918
2051
|
subject: { type: "string", maxLength: 120, description: "Optional short task subject; updates task aliases independently of human names. Also names an unnamed Pi session." },
|
|
@@ -1952,7 +2085,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1952
2085
|
if (declared && !ctx.sessionManager.getSessionName?.()) pi.setSessionName(declared);
|
|
1953
2086
|
const alias = sanitizeDisplayLabel(ctx.sessionManager.getSessionName?.() ?? "");
|
|
1954
2087
|
const aliases = stateCache.get(ctx.sessionManager)?.aliases;
|
|
1955
|
-
if (state !== undefined || declared)
|
|
2088
|
+
if (state !== undefined || declared) publishActivityPrompt();
|
|
1956
2089
|
presence?.refreshLabel();
|
|
1957
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"}`,
|
|
1958
2091
|
{ gentleAgents: { senderSessionId: transport.sessionId, alias, sessionName: alias, initialAlias: aliases?.initialAlias ?? null, currentAlias: aliases?.currentAlias ?? null } });
|
|
@@ -2073,8 +2206,11 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2073
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)` : ""}`;
|
|
2074
2207
|
});
|
|
2075
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 } });
|
|
2076
|
-
} catch {
|
|
2077
|
-
|
|
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 });
|
|
2078
2214
|
}
|
|
2079
2215
|
},
|
|
2080
2216
|
});
|
|
@@ -2132,8 +2268,9 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2132
2268
|
recipient = candidates[index].sessionId;
|
|
2133
2269
|
activation = candidates[index];
|
|
2134
2270
|
}
|
|
2135
|
-
} catch {
|
|
2136
|
-
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
const reason = error instanceof SessionPresenceError ? error.code : "unknown";
|
|
2273
|
+
return text(`Error: session discovery is unavailable (${reason}).`, { error: "unavailable", reason });
|
|
2137
2274
|
}
|
|
2138
2275
|
}
|
|
2139
2276
|
if (recipient === undefined || !validTransportSessionId(recipient)) return text("Error: recipient session ID or message is invalid.", { error: "invalid input" });
|
|
@@ -2283,11 +2420,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2283
2420
|
if (collapseKey) {
|
|
2284
2421
|
pi.registerShortcut(collapseKey as Parameters<ExtensionAPI["registerShortcut"]>[0], {
|
|
2285
2422
|
description: "Collapse or expand the agents card",
|
|
2286
|
-
handler: async () =>
|
|
2287
|
-
collapsed = !collapsed;
|
|
2288
|
-
if (sidebarTui) invalidateSidebar(sidebarTui);
|
|
2289
|
-
host?.requestRender();
|
|
2290
|
-
},
|
|
2423
|
+
handler: async () => toggleCollapsed(),
|
|
2291
2424
|
});
|
|
2292
2425
|
}
|
|
2293
2426
|
|
|
@@ -2343,7 +2476,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2343
2476
|
helperPermission.clear();
|
|
2344
2477
|
if (sessions !== ctx.sessionManager) return;
|
|
2345
2478
|
stateCache.load(ctx.sessionManager);
|
|
2346
|
-
|
|
2479
|
+
publishActivityPrompt();
|
|
2347
2480
|
});
|
|
2348
2481
|
pi.on("session_start", async (event, ctx) => {
|
|
2349
2482
|
// gentle-shell#1064 slice 3b-ii: a parent session that has no explicit
|
|
@@ -2385,7 +2518,8 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2385
2518
|
if (preexisting && sessionId) void restoreSessionHistory(ctx, sessionId);
|
|
2386
2519
|
try {
|
|
2387
2520
|
presence = startPresence(ctx.sessionManager);
|
|
2388
|
-
|
|
2521
|
+
resetActivityPublish();
|
|
2522
|
+
publishActivityPrompt();
|
|
2389
2523
|
} catch { presence = undefined; }
|
|
2390
2524
|
void startSessionTransport(ctx);
|
|
2391
2525
|
// The desktop app's own pi process: publish live subagent state through
|
|
@@ -2428,6 +2562,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2428
2562
|
rpcActivityPublisher?.stop();
|
|
2429
2563
|
rpcActivityPublisher = undefined;
|
|
2430
2564
|
cancelClock?.();
|
|
2565
|
+
resetActivityPublish();
|
|
2431
2566
|
for (const view of overlays) { view.handleInput("q"); view.dispose(); }
|
|
2432
2567
|
overlays.clear();
|
|
2433
2568
|
sessions = undefined;
|