gentle-pi 4.3.0 → 4.5.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 +10 -7
- 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 +138 -155
- 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 +82 -0
- package/docs/gentle-shell.md +80 -10
- package/docs/install-wizard.md +30 -6
- package/docs/permission-modes.md +7 -2
- package/docs/readme-reference.md +72 -55
- 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 +187 -40
- package/extensions/gentle-ai.ts +1765 -1185
- package/extensions/gentle-design.ts +158 -0
- package/extensions/gentle-music.ts +354 -0
- package/extensions/gentle-shell.ts +418 -79
- 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 +3 -2
- 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 +141 -57
- package/lib/gentle-shell-launcher.ts +11 -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/jobs-sidebar-state.ts +22 -3
- package/lib/jobs-widget.ts +112 -0
- 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-freeze.ts +2 -2
- 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 +6 -7
- package/lib/shell-changes.ts +81 -5
- package/lib/shell-prompt.ts +4 -4
- package/lib/shell-sidebar-layout.ts +27 -7
- package/lib/shell-todo.ts +1 -1
- package/lib/verifier-admission.ts +5 -4
- package/lib/vim-editor-adapter.ts +14 -10
- 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 +140 -56
- package/runtime/gentle-shell-launcher.mjs +11 -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/bundled-install.mjs +209 -13
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/installer-downloads.mjs +31 -7
- package/scripts/installer-preflight.mjs +6 -4
- package/scripts/installer-windows.mjs +37 -7
- package/scripts/main-channel.mjs +91 -17
- package/scripts/pi-update.mjs +37 -7
- 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 +276 -5
- package/tests/clipboard-image.test.ts +114 -0
- package/tests/command-palette.test.ts +4 -4
- 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 +369 -24
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-ai.test.ts +928 -448
- package/tests/gentle-design.test.ts +1313 -0
- package/tests/gentle-music.test.ts +452 -0
- package/tests/gentle-shell-bundled.test.ts +321 -248
- package/tests/gentle-shell-launcher.test.ts +29 -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 +227 -27
- 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-downloads.test.ts +32 -0
- 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/jobs-widget.test.ts +138 -0
- package/tests/main-channel-upgrade.test.ts +232 -2
- package/tests/main-channel.test.ts +140 -21
- package/tests/model-profiles-preservation.test.ts +2 -2
- 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 +104 -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/runtime-harness.mjs +188 -211
- 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 +6 -4
- package/tests/session-scratch.test.ts +68 -2
- package/tests/session-worktree-registry.test.ts +13 -1
- package/tests/shell-bar.test.ts +18 -6
- 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 +67 -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/vim-editor-adapter.test.ts +2 -2
- 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";
|
|
@@ -19,14 +19,16 @@ import { createJobRegistry, isStandaloneSleep, type JobExecOperations, type JobR
|
|
|
19
19
|
import { JOB_GLYPH, JOB_NOTICE_TYPE, jobDetails, jobNoticeText, monitorEventsText, monitorStoppedText, registerBackgroundJobTools } from "../lib/background-jobs-tools.ts";
|
|
20
20
|
import { createMonitorController, mergeMonitorBatches, type MonitorNotice } from "../lib/background-monitor.ts";
|
|
21
21
|
import { JobsView } from "../lib/jobs-view.ts";
|
|
22
|
+
import { JOBS_FINISHED_TTL_MS } from "../lib/jobs-widget.ts";
|
|
22
23
|
import { JOBS_SIDEBAR_EVENT, JOBS_STATUS_KEY } from "../lib/jobs-sidebar-state.ts";
|
|
23
|
-
import { Text, type TUI } from "@earendil-works/pi-tui";
|
|
24
|
+
import { Text, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
24
25
|
import { invalidateSidebar } from "../lib/shell-sidebar-layout.ts";
|
|
25
|
-
import { VISUAL_SETTINGS_CHANGED } from "../lib/shell-sidebar.ts";
|
|
26
|
+
import { sidebarPart, VISUAL_SETTINGS_CHANGED } from "../lib/shell-sidebar.ts";
|
|
27
|
+
import { NativePointerRegion } from "../lib/native-pointer-region.ts";
|
|
26
28
|
import { resolveVisualSettings } from "../lib/visual-customization-policy.ts";
|
|
27
29
|
import { createCompletionQueue } from "../lib/agents-completion-delivery.ts";
|
|
28
30
|
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";
|
|
31
|
+
import { AGENT_MODE, PROFILE_SOURCE, discoverAgents, formatModelRef, loadAgentsConfig, resolveAgentProfile, unmatchedProfileKeys, withPinnedModelProfiles, type AgentDefinition, type AgentMode, type ProfileSource } from "../lib/agents-config.ts";
|
|
30
32
|
import { freezeSessionProfileAtStartup, inheritedProfileDriftNotice, resolveSessionProfile, sessionProfileRoutingAt, type InheritedProfileOptions, type SessionProfileResolution } from "../lib/session-profile-freeze.ts";
|
|
31
33
|
import { resolveProfilePin, resolveUnversionedProjectProfile } from "../lib/agent-profile-pin.ts";
|
|
32
34
|
import { resolveBackgroundSubagentsPolicy } from "../lib/background-subagents-policy.ts";
|
|
@@ -35,7 +37,7 @@ import { isFinished, MISSING_TOOLS_NOTE_PREFIX, TASK_EVENT, TASK_STATUS, TaskSto
|
|
|
35
37
|
import { AgentRunner, piCommand, abortReasonText, REQUESTED_TOOLS_ENV, type AskAnswer, type RunnerDeps, type TaskRequest } from "../lib/agents-runner.ts";
|
|
36
38
|
import { parseChildPackageInjection } from "../lib/child-package-injection.ts";
|
|
37
39
|
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";
|
|
40
|
+
import { ActiveSessionClient, ActiveSessionListener, SessionPresenceError, SessionPresenceRegistry, type PresenceRecord, type ReceivedNotification, type SentNotification, type SessionPresenceCandidate } from "../lib/agents-session-transport.ts";
|
|
39
41
|
import { WindowsActiveSessionClient, WindowsActiveSessionListener, WindowsSessionPresenceRegistry, type WindowsSessionRegistryPhaseObserver } from "../lib/windows-session-transport.ts";
|
|
40
42
|
import { hasReviewSessionPermission, resolveCanonicalGitRepositoryIdentitySync, type ReviewSessionManager } from "../lib/review-session-standing-permission.ts";
|
|
41
43
|
import { inheritedUnsafeGitEnvironmentKeys } from "../lib/review-repository.ts";
|
|
@@ -58,8 +60,8 @@ import { OrchestratorScopeCache, type RepositoryFact } from "../lib/orchestrator
|
|
|
58
60
|
import { createRpcActivityPublisher, type RpcActivityPublisher } from "../lib/agents-rpc-publisher.ts";
|
|
59
61
|
import { isInteractiveRpcHost } from "../lib/rpc-host.ts";
|
|
60
62
|
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";
|
|
63
|
+
import { AGENTS_GLYPH, agentsCardTone, renderAgentsCard, widgetExpiryMs, widgetRows } from "../lib/agents-widget.ts";
|
|
64
|
+
import { CARD_TONE, panelHeaderRow, renderCard } from "../lib/shell-card.ts";
|
|
63
65
|
import { openInExternalEditor } from "./gentle-shell.ts";
|
|
64
66
|
import { gentlePiConfigHome } from "../lib/agent-home.ts";
|
|
65
67
|
import { resolveAgentHomeDirectory } from "../lib/agent-model-resolution.ts";
|
|
@@ -561,7 +563,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
561
563
|
};
|
|
562
564
|
return PresencePublisher.start({ profile: agentHome, sessionId, label: labelSource(), labelSource, activity: [] });
|
|
563
565
|
};
|
|
564
|
-
const
|
|
566
|
+
const publishActivityNow = (deferred = false) => {
|
|
565
567
|
if (!sessions) return;
|
|
566
568
|
try {
|
|
567
569
|
if (!presence || presence.error) {
|
|
@@ -580,14 +582,59 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
580
582
|
return data?.sessionId === activeSessionId() && typeof data.root === "string" && typeof data.evidence === "string" ? [data.root] : [];
|
|
581
583
|
});
|
|
582
584
|
presence?.updateDiscovery(transport.listener.record, { workspace: sessions.getCwd(), tasks, registered,
|
|
583
|
-
|
|
585
|
+
// A deferred (render-cadence trailing) publish must never resolve Git:
|
|
586
|
+
// its timer can land while a peer metadata query is being served, and
|
|
587
|
+
// that contract adds no caller Git probes. It reuses the snapshot the
|
|
588
|
+
// last event-time publication projected; the next event-time publish
|
|
589
|
+
// refreshes the facts.
|
|
590
|
+
scope: deferred ? scopeCache.cachedScope() : scopeCache.project(sessions.getCwd(), tasks, registered), state: stateCache.get(sessions) });
|
|
584
591
|
}
|
|
585
592
|
} catch { presence?.dispose(); presence = undefined; }
|
|
586
593
|
};
|
|
594
|
+
// Coalesced like requestRender: the first call publishes immediately so an
|
|
595
|
+
// idle→working transition or a resumed session never waits, and every call
|
|
596
|
+
// inside the window collapses into one trailing publish, so a burst of child
|
|
597
|
+
// deltas costs one publication instead of one per event.
|
|
598
|
+
let activityPublishQueued = false;
|
|
599
|
+
let activityPublishTrailing = false;
|
|
600
|
+
let cancelActivityPublish: (() => void) | undefined;
|
|
601
|
+
const resetActivityPublish = () => {
|
|
602
|
+
cancelActivityPublish?.();
|
|
603
|
+
cancelActivityPublish = undefined;
|
|
604
|
+
activityPublishQueued = false;
|
|
605
|
+
activityPublishTrailing = false;
|
|
606
|
+
};
|
|
607
|
+
const flushDeferredPublish = () => {
|
|
608
|
+
cancelActivityPublish = undefined;
|
|
609
|
+
activityPublishQueued = false;
|
|
610
|
+
if (!activityPublishTrailing) return;
|
|
611
|
+
activityPublishTrailing = false;
|
|
612
|
+
publishActivityNow(true);
|
|
613
|
+
};
|
|
614
|
+
const publishActivity = () => {
|
|
615
|
+
if (activityPublishQueued) {
|
|
616
|
+
activityPublishTrailing = true;
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
publishActivityNow();
|
|
620
|
+
activityPublishQueued = true;
|
|
621
|
+
cancelActivityPublish = deps.schedule(flushDeferredPublish, RENDER_COALESCE_MS);
|
|
622
|
+
};
|
|
623
|
+
// Discrete semantic events publish immediately so peers and readers see them
|
|
624
|
+
// promptly (a consult binds to the published snapshot); the coalescing window
|
|
625
|
+
// stays armed so a child-delta burst that follows still collapses into one
|
|
626
|
+
// trailing publish.
|
|
627
|
+
const publishActivityPrompt = () => {
|
|
628
|
+
publishActivityNow();
|
|
629
|
+
if (!activityPublishQueued) {
|
|
630
|
+
activityPublishQueued = true;
|
|
631
|
+
cancelActivityPublish = deps.schedule(flushDeferredPublish, RENDER_COALESCE_MS);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
587
634
|
const unsubscribeScope = pi.events.on(SESSION_WORKTREE_CHANGED, (event) => {
|
|
588
635
|
if ((event as { sessionId?: string } | undefined)?.sessionId !== sessions?.getSessionId()) return;
|
|
589
636
|
scopeCache.clear();
|
|
590
|
-
|
|
637
|
+
publishActivityPrompt();
|
|
591
638
|
});
|
|
592
639
|
// The todo list is the session's progress: publish it for peers so the model
|
|
593
640
|
// spends no turn on orchestrator_session_id. Merged into the owner's curated
|
|
@@ -736,7 +783,11 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
736
783
|
await closeStartupTransport();
|
|
737
784
|
return;
|
|
738
785
|
}
|
|
739
|
-
|
|
786
|
+
// Publish immediately, not on the coalescing trailing edge: this is the
|
|
787
|
+
// session's first discovery publication, and a peer metadata query binds
|
|
788
|
+
// to the activation only through it — a 400 ms lag here makes a prompt
|
|
789
|
+
// consult read publication-unknown-or-stale.
|
|
790
|
+
publishActivityPrompt();
|
|
740
791
|
} catch {
|
|
741
792
|
if (activeSessionTransport?.generation === generation) activeSessionTransport = undefined;
|
|
742
793
|
await closeStartupTransport();
|
|
@@ -1181,11 +1232,22 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1181
1232
|
// Structured descriptions feed Status; native hosts keep the compact count.
|
|
1182
1233
|
const refreshJobStatus = () => {
|
|
1183
1234
|
const sessionId = activeSessionId();
|
|
1184
|
-
const
|
|
1235
|
+
const sessionJobs = jobs.list(sessionId ?? "");
|
|
1236
|
+
const running = sessionJobs.filter((job) => job.status === "running")
|
|
1185
1237
|
.sort((a, b) => b.startedAt - a.startedAt);
|
|
1238
|
+
// Finished tails ride the same snapshot so the sidebar card can linger
|
|
1239
|
+
// them briefly, like the Agents card's results; failed jobs ride too,
|
|
1240
|
+
// painting the ✗ glyph like a nonzero exit.
|
|
1241
|
+
const finished = sessionJobs.filter((job) => (job.status === "exited" || job.status === "stopped" || job.status === "failed") && job.endedAt !== undefined && deps.now() - job.endedAt < JOBS_FINISHED_TTL_MS)
|
|
1242
|
+
.sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0)).slice(0, 3);
|
|
1186
1243
|
try { parentCtx?.ui.setStatus(JOBS_STATUS_KEY, running.length === 0 ? undefined : `${JOB_GLYPH} ${running.length} job${running.length === 1 ? "" : "s"}`); } catch { /* Status is cosmetic. */ }
|
|
1187
1244
|
if (sessionId) {
|
|
1188
|
-
try {
|
|
1245
|
+
try {
|
|
1246
|
+
pi.events.emit(JOBS_SIDEBAR_EVENT, { sessionId, snapshot: { jobs: [
|
|
1247
|
+
...running.map(({ id, label, startedAt }) => ({ id, label, startedAt })),
|
|
1248
|
+
...finished.map(({ id, label, startedAt, status, endedAt, exitCode }) => ({ id, label, startedAt, status, endedAt, exitCode })),
|
|
1249
|
+
] } });
|
|
1250
|
+
} catch { /* Display only. */ }
|
|
1189
1251
|
}
|
|
1190
1252
|
};
|
|
1191
1253
|
|
|
@@ -1565,7 +1627,11 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1565
1627
|
ctx.ui.notify(`Could not read the task's session: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
1566
1628
|
return;
|
|
1567
1629
|
}
|
|
1568
|
-
|
|
1630
|
+
try {
|
|
1631
|
+
if (!openInExternalEditor(overlayHost, transcriptPath)) ctx.ui.notify("No editor configured. Set $VISUAL or $EDITOR.", "warning");
|
|
1632
|
+
} catch (error) {
|
|
1633
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
1634
|
+
}
|
|
1569
1635
|
};
|
|
1570
1636
|
|
|
1571
1637
|
const writeTranscript = async (task: TaskRecord): Promise<string> => {
|
|
@@ -1597,24 +1663,80 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1597
1663
|
host?.requestRender();
|
|
1598
1664
|
});
|
|
1599
1665
|
tickClock();
|
|
1600
|
-
// Agents
|
|
1601
|
-
//
|
|
1602
|
-
// the
|
|
1666
|
+
// Agents follows Todos: the fullscreen rail shows it when the terminal is
|
|
1667
|
+
// wide enough, and sidebarPart suppresses the above-editor copy only
|
|
1668
|
+
// while the rail owns the host; narrower terminals keep the widget.
|
|
1603
1669
|
ui?.setWidget(AGENTS_WIDGET_KEY, (tui, theme) => {
|
|
1604
1670
|
host = tui;
|
|
1605
1671
|
sidebarTui = tui;
|
|
1606
|
-
return
|
|
1607
|
-
render(width: number) {
|
|
1608
|
-
if (!agentsVisible) return [];
|
|
1609
|
-
const lines = renderAgentsCard(visibleTasks(), theme, width, deps.now(), { collapsed, collapseKey, maxRows: widgetRows(tui.terminal?.rows), viewKey });
|
|
1610
|
-
return lines.length === 0 ? [] : [...lines, ""];
|
|
1611
|
-
},
|
|
1612
|
-
invalidate() {},
|
|
1613
|
-
};
|
|
1672
|
+
return sidebarPart(tui, "agents", agentsCard(tui, theme, false), agentsCard(tui, theme, true));
|
|
1614
1673
|
});
|
|
1615
1674
|
};
|
|
1616
1675
|
|
|
1676
|
+
const toggleCollapsed = () => {
|
|
1677
|
+
collapsed = !collapsed;
|
|
1678
|
+
if (sidebarTui) invalidateSidebar(sidebarTui);
|
|
1679
|
+
host?.requestRender();
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1682
|
+
// One surface of the Agents card. The rail is natively scrollable, so it
|
|
1683
|
+
// lists every task; the above-editor widget keeps its row budget. Only the
|
|
1684
|
+
// header row is the clickable collapse control, exactly like Todos.
|
|
1685
|
+
const agentsCard = (tui: TUI, theme: Parameters<typeof renderAgentsCard>[1], rail: boolean): Component & { dispose(): void; digest(): string } => {
|
|
1686
|
+
let hovered = false;
|
|
1687
|
+
let headerRow = 0;
|
|
1688
|
+
const card: Component = {
|
|
1689
|
+
render(width: number) {
|
|
1690
|
+
if (!agentsVisible) return [];
|
|
1691
|
+
const tasks = visibleTasks();
|
|
1692
|
+
const now = deps.now();
|
|
1693
|
+
headerRow = panelHeaderRow(theme, width, agentsCardTone(tasks, now));
|
|
1694
|
+
const lines = renderAgentsCard(tasks, theme, width, now, { collapsed, collapseKey, maxRows: rail ? undefined : widgetRows(tui.terminal?.rows), viewKey, collapseControl: true, hovered });
|
|
1695
|
+
return lines.length === 0 || rail ? lines : [...lines, ""];
|
|
1696
|
+
},
|
|
1697
|
+
invalidate() {
|
|
1698
|
+
hovered = false;
|
|
1699
|
+
},
|
|
1700
|
+
};
|
|
1701
|
+
const region = new NativePointerRegion(card, {
|
|
1702
|
+
onHover(event) {
|
|
1703
|
+
const next = event.y === headerRow;
|
|
1704
|
+
if (next === hovered) return { handled: true };
|
|
1705
|
+
hovered = next;
|
|
1706
|
+
return { handled: true, render: true };
|
|
1707
|
+
},
|
|
1708
|
+
onLeave() {
|
|
1709
|
+
if (!hovered) return;
|
|
1710
|
+
hovered = false;
|
|
1711
|
+
host?.requestRender();
|
|
1712
|
+
},
|
|
1713
|
+
onClick(event) {
|
|
1714
|
+
if (event.button !== "left" || event.y !== headerRow) return undefined;
|
|
1715
|
+
toggleCollapsed();
|
|
1716
|
+
return { handled: true, render: true };
|
|
1717
|
+
},
|
|
1718
|
+
});
|
|
1719
|
+
return {
|
|
1720
|
+
// The rail memo repaints a part only when its digest moves: task
|
|
1721
|
+
// state, the local controls, and the elapsed clock (to the second)
|
|
1722
|
+
// only while a task is still running, so an idle or finished card
|
|
1723
|
+
// never forces a rail rebuild every second.
|
|
1724
|
+
digest: () => {
|
|
1725
|
+
const tasks = visibleTasks();
|
|
1726
|
+
const clock = tasks.some((task) => !isFinished(task.status)) ? Math.floor(deps.now() / 1000) : 0;
|
|
1727
|
+
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])]);
|
|
1728
|
+
},
|
|
1729
|
+
render: (width) => region.render(width),
|
|
1730
|
+
handleMouse: (event) => region.handleMouse(event),
|
|
1731
|
+
invalidate: () => region.invalidate(),
|
|
1732
|
+
dispose: () => region.dispose(),
|
|
1733
|
+
};
|
|
1734
|
+
};
|
|
1735
|
+
|
|
1617
1736
|
const roots = (ctx: ExtensionContext) => ({ cwd: ctx.sessionManager.getCwd(), home: deps.home, agentHome });
|
|
1737
|
+
// Issue #2400: parent sessions already told about model_profiles keys that
|
|
1738
|
+
// match no agent and suggest none, so the notice fires once per session.
|
|
1739
|
+
const unmatchedKeysNotifiedSessions = new Set<string>();
|
|
1618
1740
|
|
|
1619
1741
|
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
1742
|
if (retiredSddAgent(agent.name)) throw new Error("Retired SDD agents cannot be dispatched.");
|
|
@@ -1678,7 +1800,8 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1678
1800
|
// same-clone registry. Never use a caller-injected resolver for this identity.
|
|
1679
1801
|
const identity = resolveSessionWorktree(selectedRoot, parentCwd);
|
|
1680
1802
|
const canonicalParent = resolveSessionWorktree(parentCwd, parentCwd);
|
|
1681
|
-
|
|
1803
|
+
const spelling = nativeRootSpelling(selectedRoot);
|
|
1804
|
+
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
1805
|
await foreignGrants.authorize(ctx, identity, { continuation: resume !== undefined, signal });
|
|
1683
1806
|
foreignIdentity = identity;
|
|
1684
1807
|
foreignParent = canonicalParent;
|
|
@@ -1729,6 +1852,28 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1729
1852
|
if (drift !== undefined) ctx.ui.notify(drift, "info");
|
|
1730
1853
|
}
|
|
1731
1854
|
const profile = resolveAgentProfile(agent, config);
|
|
1855
|
+
// Issue #2196 (PR #2233 maintainer decision): a model_profiles key that
|
|
1856
|
+
// names no discovered agent was silently ignored while the child inherited
|
|
1857
|
+
// the orchestrator model. When a configured key suggests this agent, name
|
|
1858
|
+
// it at launch; resolution stays exact-match, so an omitted role keeps
|
|
1859
|
+
// inheriting and a matched key never warns. Keys are checked against every
|
|
1860
|
+
// discovered agent, so a key naming another agent is never reported here.
|
|
1861
|
+
if (ctx.hasUI) {
|
|
1862
|
+
const unmatched = unmatchedProfileKeys(config.modelProfiles, discoverAgents(roots(ctx)).agents);
|
|
1863
|
+
const nearMisses = unmatched.filter((entry) => entry.suggestion === agent.name);
|
|
1864
|
+
if (nearMisses.length > 0) {
|
|
1865
|
+
ctx.ui.notify(nearMisses.map((entry) => `"${entry.key}" did not match any agent, did you mean ${agent.name}?`).join(" "), "warning");
|
|
1866
|
+
}
|
|
1867
|
+
// Issue #2400: a key with no suggestion never names the launched agent,
|
|
1868
|
+
// so the filter above kept it silent. The session's first launch lists
|
|
1869
|
+
// every such key once; later launches in the same session stay quiet.
|
|
1870
|
+
const unsuggested = unmatched.filter((entry) => entry.suggestion === undefined);
|
|
1871
|
+
const parentSession = ctx.sessionManager.getSessionId() ?? "";
|
|
1872
|
+
if (unsuggested.length > 0 && !unmatchedKeysNotifiedSessions.has(parentSession)) {
|
|
1873
|
+
unmatchedKeysNotifiedSessions.add(parentSession);
|
|
1874
|
+
ctx.ui.notify(unsuggested.map((entry) => `"${sanitizeTerminalText(entry.key)}" did not match any agent and was ignored.`).join(" "), "warning");
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1732
1877
|
// #2180: the winning layer is named from the resolution and routing this
|
|
1733
1878
|
// request already performed, never by re-resolving them.
|
|
1734
1879
|
const modelSource = modelSourceToken(profile.source.model, sessionProfile, routingTarget);
|
|
@@ -1839,7 +1984,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1839
1984
|
if (launched && foreignRequest) foreignTasks.set(task.id, foreignRequest);
|
|
1840
1985
|
ownedTaskIds.add(task.id);
|
|
1841
1986
|
publishWork?.(task.id);
|
|
1842
|
-
|
|
1987
|
+
publishActivityPrompt(); // Admission's summary notification precedes runtime ownership.
|
|
1843
1988
|
store.subscribe(task.id, () => { publishActivity(); requestRender(); });
|
|
1844
1989
|
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
1990
|
// A tool call aborted by the host (a human interrupting the turn, a timeout)
|
|
@@ -1912,7 +2057,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1912
2057
|
pi.registerTool({
|
|
1913
2058
|
name: "orchestrator_session_id",
|
|
1914
2059
|
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.",
|
|
2060
|
+
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
2061
|
...orchestratorToolRenderers("session", expandHint),
|
|
1917
2062
|
parameters: { type: "object", additionalProperties: false, properties: {
|
|
1918
2063
|
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 +2097,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1952
2097
|
if (declared && !ctx.sessionManager.getSessionName?.()) pi.setSessionName(declared);
|
|
1953
2098
|
const alias = sanitizeDisplayLabel(ctx.sessionManager.getSessionName?.() ?? "");
|
|
1954
2099
|
const aliases = stateCache.get(ctx.sessionManager)?.aliases;
|
|
1955
|
-
if (state !== undefined || declared)
|
|
2100
|
+
if (state !== undefined || declared) publishActivityPrompt();
|
|
1956
2101
|
presence?.refreshLabel();
|
|
1957
2102
|
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
2103
|
{ gentleAgents: { senderSessionId: transport.sessionId, alias, sessionName: alias, initialAlias: aliases?.initialAlias ?? null, currentAlias: aliases?.currentAlias ?? null } });
|
|
@@ -2073,8 +2218,11 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2073
2218
|
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
2219
|
});
|
|
2075
2220
|
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
|
-
|
|
2221
|
+
} catch (error) {
|
|
2222
|
+
// SessionPresenceError codes are a fixed enum with no record content,
|
|
2223
|
+
// so surfacing the reason is safe and keeps diagnosis possible.
|
|
2224
|
+
const reason = error instanceof SessionPresenceError ? error.code : "unknown";
|
|
2225
|
+
return text(`Error: session discovery is unavailable (${reason}).`, { error: "unavailable", reason });
|
|
2078
2226
|
}
|
|
2079
2227
|
},
|
|
2080
2228
|
});
|
|
@@ -2132,8 +2280,9 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2132
2280
|
recipient = candidates[index].sessionId;
|
|
2133
2281
|
activation = candidates[index];
|
|
2134
2282
|
}
|
|
2135
|
-
} catch {
|
|
2136
|
-
|
|
2283
|
+
} catch (error) {
|
|
2284
|
+
const reason = error instanceof SessionPresenceError ? error.code : "unknown";
|
|
2285
|
+
return text(`Error: session discovery is unavailable (${reason}).`, { error: "unavailable", reason });
|
|
2137
2286
|
}
|
|
2138
2287
|
}
|
|
2139
2288
|
if (recipient === undefined || !validTransportSessionId(recipient)) return text("Error: recipient session ID or message is invalid.", { error: "invalid input" });
|
|
@@ -2283,11 +2432,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2283
2432
|
if (collapseKey) {
|
|
2284
2433
|
pi.registerShortcut(collapseKey as Parameters<ExtensionAPI["registerShortcut"]>[0], {
|
|
2285
2434
|
description: "Collapse or expand the agents card",
|
|
2286
|
-
handler: async () =>
|
|
2287
|
-
collapsed = !collapsed;
|
|
2288
|
-
if (sidebarTui) invalidateSidebar(sidebarTui);
|
|
2289
|
-
host?.requestRender();
|
|
2290
|
-
},
|
|
2435
|
+
handler: async () => toggleCollapsed(),
|
|
2291
2436
|
});
|
|
2292
2437
|
}
|
|
2293
2438
|
|
|
@@ -2343,7 +2488,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2343
2488
|
helperPermission.clear();
|
|
2344
2489
|
if (sessions !== ctx.sessionManager) return;
|
|
2345
2490
|
stateCache.load(ctx.sessionManager);
|
|
2346
|
-
|
|
2491
|
+
publishActivityPrompt();
|
|
2347
2492
|
});
|
|
2348
2493
|
pi.on("session_start", async (event, ctx) => {
|
|
2349
2494
|
// gentle-shell#1064 slice 3b-ii: a parent session that has no explicit
|
|
@@ -2385,7 +2530,8 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2385
2530
|
if (preexisting && sessionId) void restoreSessionHistory(ctx, sessionId);
|
|
2386
2531
|
try {
|
|
2387
2532
|
presence = startPresence(ctx.sessionManager);
|
|
2388
|
-
|
|
2533
|
+
resetActivityPublish();
|
|
2534
|
+
publishActivityPrompt();
|
|
2389
2535
|
} catch { presence = undefined; }
|
|
2390
2536
|
void startSessionTransport(ctx);
|
|
2391
2537
|
// The desktop app's own pi process: publish live subagent state through
|
|
@@ -2428,6 +2574,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
2428
2574
|
rpcActivityPublisher?.stop();
|
|
2429
2575
|
rpcActivityPublisher = undefined;
|
|
2430
2576
|
cancelClock?.();
|
|
2577
|
+
resetActivityPublish();
|
|
2431
2578
|
for (const view of overlays) { view.handleInput("q"); view.dispose(); }
|
|
2432
2579
|
overlays.clear();
|
|
2433
2580
|
sessions = undefined;
|