@pellux/goodvibes-tui 1.16.1 → 1.18.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/CHANGELOG.md +138 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +5490 -1806
- package/package.json +7 -7
- package/src/cli/completions/generate.ts +5 -4
- package/src/cli/config-overrides.ts +51 -11
- package/src/cli/doctor.ts +4 -5
- package/src/cli/entrypoint.ts +13 -8
- package/src/cli/help.ts +7 -6
- package/src/cli/launch-auto-update.ts +214 -0
- package/src/cli/management-commands.ts +82 -15
- package/src/cli/management.ts +0 -2
- package/src/cli/parser.ts +5 -0
- package/src/cli/status.ts +13 -0
- package/src/cli/surface-command.ts +5 -5
- package/src/cli/tui-startup.ts +60 -97
- package/src/cli/types.ts +2 -0
- package/src/config/index.ts +1 -1
- package/src/config/tui-extension-settings.ts +33 -0
- package/src/core/composer-state.ts +1 -1
- package/src/core/consolidation-receipt.ts +41 -0
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +18 -9
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/memory-provenance.ts +67 -0
- package/src/core/pairing-devices.ts +49 -0
- package/src/core/pairing-handoff.ts +101 -0
- package/src/core/pairing-offers.ts +83 -0
- package/src/core/pairing-origin.ts +78 -0
- package/src/core/pairing-tailscale-gateway.ts +56 -0
- package/src/core/power-chip-source.ts +91 -0
- package/src/core/power-status.ts +95 -0
- package/src/core/stable-host.ts +171 -0
- package/src/core/stream-event-wiring.ts +29 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-budget-outcome.ts +60 -0
- package/src/core/turn-cancellation.ts +40 -1
- package/src/core/turn-event-wiring.ts +14 -8
- package/src/daemon/cli.ts +64 -58
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/service-commands.ts +5 -5
- package/src/export/cost-utils.ts +128 -6
- package/src/input/autocomplete.ts +2 -2
- package/src/input/command-registry.ts +55 -9
- package/src/input/commands/acp-runtime.ts +72 -0
- package/src/input/commands/ci-runtime.ts +5 -1
- package/src/input/commands/codebase-runtime.ts +6 -6
- package/src/input/commands/cost-runtime.ts +1 -1
- package/src/input/commands/devices-runtime.ts +109 -0
- package/src/input/commands/git-runtime.ts +28 -4
- package/src/input/commands/graph-runtime.ts +45 -0
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/intelligence-runtime.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +1 -1
- package/src/input/commands/local-provider-runtime.ts +49 -10
- package/src/input/commands/local-runtime.ts +53 -5
- package/src/input/commands/memory.ts +2 -2
- package/src/input/commands/operator-panel-runtime.ts +5 -5
- package/src/input/commands/operator-rpc.ts +32 -11
- package/src/input/commands/permissions-provenance.ts +1 -1
- package/src/input/commands/permissions-runtime.ts +99 -8
- package/src/input/commands/planning-runtime.ts +3 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -1
- package/src/input/commands/power-runtime.ts +56 -0
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/qrcode-runtime.ts +14 -19
- package/src/input/commands/queue-runtime.ts +70 -0
- package/src/input/commands/recall-files-git.ts +13 -51
- package/src/input/commands/recall-files.ts +8 -11
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/relay-runtime.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +4 -4
- package/src/input/commands/schedule-runtime.ts +14 -7
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +2 -2
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +46 -68
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/update-runtime.ts +153 -96
- package/src/input/commands/work-plan-runtime.ts +1 -1
- package/src/input/commands/workstream-attempts.ts +6 -1
- package/src/input/commands/workstream-runtime.ts +46 -27
- package/src/input/commands/worktree-runtime.ts +35 -8
- package/src/input/commands.ts +10 -2
- package/src/input/config-modal-types.ts +14 -3
- package/src/input/config-modal.ts +75 -13
- package/src/input/feature-unit-layout.ts +93 -82
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/handler-command-route.ts +2 -2
- package/src/input/handler-content-actions.ts +1 -1
- package/src/input/handler-feed-routes.ts +7 -7
- package/src/input/handler-feed.ts +4 -4
- package/src/input/handler-interactions.ts +3 -2
- package/src/input/handler-modal-routes.ts +12 -4
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
- package/src/input/handler-onboarding.ts +3 -2
- package/src/input/handler-picker-routes.ts +26 -13
- package/src/input/handler-shortcuts.ts +29 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +21 -3
- package/src/input/onboarding/onboarding-feature-units.ts +34 -27
- package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
- package/src/input/panel-integration-actions.ts +2 -2
- package/src/input/panel-mouse-geometry.ts +1 -1
- package/src/input/panel-paste-flood-guard.ts +1 -1
- package/src/input/provider-key-intake.ts +167 -0
- package/src/input/sandbox-exec-config.ts +2 -2
- package/src/input/selection-modal.ts +1 -1
- package/src/input/settings-modal-activation.ts +5 -0
- package/src/input/settings-modal-adjustment.ts +4 -31
- package/src/input/settings-modal-data.ts +52 -57
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +27 -17
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +90 -90
- package/src/panels/agent-inspector-shared.ts +4 -4
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/agent.ts +4 -4
- package/src/panels/builtin/development.ts +4 -4
- package/src/panels/builtin/knowledge.ts +1 -1
- package/src/panels/builtin/operations.ts +83 -11
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +16 -4
- package/src/panels/builtin-modals.ts +59 -42
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +39 -6
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-acts.ts +446 -0
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-gateway.ts +117 -0
- package/src/panels/fleet-observed-render.ts +128 -0
- package/src/panels/fleet-panel-format.ts +163 -11
- package/src/panels/fleet-panel-worktree-detail.ts +14 -0
- package/src/panels/fleet-panel.ts +73 -57
- package/src/panels/fleet-read-model.ts +84 -14
- package/src/panels/fleet-spawn.ts +266 -0
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +41 -5
- package/src/panels/fleet-tabs.ts +15 -16
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/git-panel.ts +2 -2
- package/src/panels/index.ts +4 -1
- package/src/panels/local-auth-panel.ts +1 -1
- package/src/panels/memory-consolidation-gateway.ts +94 -0
- package/src/panels/modals/devices-modal.ts +161 -0
- package/src/panels/modals/hooks-modal.ts +1 -1
- package/src/panels/modals/keybindings-modal.ts +2 -2
- package/src/panels/modals/knowledge-modal.ts +1 -1
- package/src/panels/modals/marketplace-modal.ts +4 -4
- package/src/panels/modals/memory-modal.ts +153 -9
- package/src/panels/modals/modal-surface-helpers.ts +1 -1
- package/src/panels/modals/modal-theme.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +198 -68
- package/src/panels/modals/planning-modal.ts +2 -2
- package/src/panels/modals/plugins-modal.ts +1 -1
- package/src/panels/modals/policy-modal.ts +1 -1
- package/src/panels/modals/security-modal.ts +7 -1
- package/src/panels/modals/skills-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +3 -3
- package/src/panels/notifications-feed.ts +127 -0
- package/src/panels/notifications-panel.ts +160 -0
- package/src/panels/panel-manager.ts +9 -9
- package/src/panels/plugins-panel.ts +4 -4
- package/src/panels/project-planning-answer-actions.ts +1 -1
- package/src/panels/provider-health-routes.ts +2 -2
- package/src/panels/skills-panel.ts +4 -5
- package/src/panels/token-budget-panel.ts +11 -3
- package/src/panels/types.ts +5 -5
- package/src/panels/workstream-graph-render.ts +123 -0
- package/src/permissions/broker-approval-card.ts +201 -0
- package/src/permissions/hunk-selection.ts +28 -4
- package/src/permissions/prompt.ts +244 -29
- package/src/renderer/autocomplete-overlay.ts +107 -54
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compaction-history-modal.ts +1 -1
- package/src/renderer/compaction-preview.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/composer-fragments.ts +69 -0
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/footer-tips.ts +3 -3
- package/src/renderer/hint-grammar.ts +1 -1
- package/src/renderer/modal-factory.ts +1 -1
- package/src/renderer/model-picker-overlay.ts +1 -1
- package/src/renderer/model-workspace.ts +7 -2
- package/src/renderer/panel-composite.ts +2 -2
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/selection-modal-overlay.ts +1 -1
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +23 -5
- package/src/renderer/settings-modal.ts +129 -39
- package/src/renderer/shell-surface.ts +6 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +13 -7
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +38 -1
- package/src/runtime/bootstrap-command-context.ts +7 -2
- package/src/runtime/bootstrap-command-parts.ts +9 -5
- package/src/runtime/bootstrap-core.ts +41 -41
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +13 -5
- package/src/runtime/bootstrap.ts +34 -32
- package/src/runtime/channel-composition.ts +46 -0
- package/src/runtime/code-index-services.ts +3 -4
- package/src/runtime/daemon-attach-notices.ts +120 -0
- package/src/runtime/durability-services.ts +90 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/idle-power-services.ts +85 -0
- package/src/runtime/index.ts +6 -21
- package/src/runtime/interaction-seams.ts +109 -0
- package/src/runtime/legacy-daemon-migration.ts +29 -19
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/notification-dispatch.ts +110 -0
- package/src/runtime/onboarding/apply.ts +3 -29
- package/src/runtime/onboarding/fast-path.ts +39 -6
- package/src/runtime/onboarding/provider-key-capture.ts +126 -0
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +9 -3
- package/src/runtime/power-keepawake-remote.ts +75 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/provider-fallback.ts +55 -0
- package/src/runtime/provider-optimizer-wiring.ts +55 -0
- package/src/runtime/release-artifacts.ts +19 -103
- package/src/runtime/resume-notice.ts +1 -1
- package/src/runtime/services.ts +113 -113
- package/src/runtime/session-continuity-hints.ts +38 -0
- package/src/runtime/surface-feature-flags.ts +40 -9
- package/src/runtime/terminal-output-guard.ts +2 -2
- package/src/runtime/trust/workspace-registration.ts +10 -2
- package/src/runtime/trust/workspace-trust.ts +58 -31
- package/src/runtime/ui-services.ts +6 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +108 -132
- package/src/shell/recovery-input-helpers.ts +53 -166
- package/src/shell/ui-openers.ts +38 -6
- package/src/utils/format-duration.ts +1 -1
- package/src/verification/verification-ledger.ts +21 -4
- package/src/version.ts +1 -1
- package/src/input/commands/flags-runtime.ts +0 -328
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Line } from '../types/grid.ts';
|
|
9
|
-
import type { SettingsModal, SettingEntry,
|
|
9
|
+
import type { SettingsModal, SettingEntry, McpEntry, SubscriptionEntry, SettingsCategory } from '../input/settings-modal.ts';
|
|
10
10
|
import { SETTINGS_CATEGORIES, SETTINGS_CATEGORY_GROUPS } from '../input/settings-modal.ts';
|
|
11
|
+
import { FEATURE_SETTINGS_BY_ID, isFeatureValueEnabled } from '../runtime/feature-settings.ts';
|
|
11
12
|
import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
|
|
12
13
|
import { CATEGORY_LABELS, describeUiRouting, formatValue, getSettingLabel, inferSubscriptionRouteReason, valueColor } from './settings-modal-helpers.ts';
|
|
13
14
|
import { isSecretConfigKey } from '../config/secret-config.ts';
|
|
@@ -27,6 +28,8 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
27
28
|
display: 'Presentation settings for the terminal transcript: streaming, line numbers, thinking visibility, reasoning summaries, token speed, and tool previews.',
|
|
28
29
|
ui: 'Controls where operational messages render and whether voice surfaces are enabled. These settings change visibility, not provider behavior.',
|
|
29
30
|
provider: 'Default model routing for normal chat turns, embeddings, reasoning effort, and persistent system prompt file.',
|
|
31
|
+
pricing: 'Manual model prices (USD per 1M tokens, keyed provider:model). Your price wins over provider-served and catalog prices in every cost display, immediately.',
|
|
32
|
+
update: 'Self-update posture: whether the daemon checks hourly and updates at an idle moment, the release source it checks, and the check cadence. Clients update at launch; every swap keeps the previous binary for one-command rollback.',
|
|
30
33
|
subscriptions: 'Provider subscription login state and routing posture. Active sessions can be reviewed or signed out here; API keys remain managed through secrets.',
|
|
31
34
|
behavior: 'Day-to-day shell behavior: approval posture, compaction, history, guidance, notifications, stale-context warnings, return context, and Human-in-the-Loop mode.',
|
|
32
35
|
storage: 'Local storage posture, including secret storage policy and maximum artifact size for knowledge/home graph/document ingestion.',
|
|
@@ -37,6 +40,7 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
37
40
|
wrfc: 'Work-review-fix-cycle thresholds, retry limits, and automatic commit behavior.',
|
|
38
41
|
helper: 'Helper model defaults used by helper subsystems when they do not use the main chat route.',
|
|
39
42
|
tts: 'Text-to-speech provider, voice, and optional spoken-turn LLM overrides.',
|
|
43
|
+
voice: 'Free, offline local voice engines (voice.local.*): the STT and TTS engine, binary, and model-path for whisper.cpp/faster-whisper and Piper/Kokoro. All empty by default — configurable-not-configured, nothing auto-downloads, and an unconfigured machine reports honestly (never an error). Once set, "Local engines" appears in the TTS provider picker beside elevenlabs, with no billing dimension.',
|
|
40
44
|
service: 'Background service posture: enabled state, autostart, restart behavior, service name, platform, and logs.',
|
|
41
45
|
daemon: 'Local session daemon. It hosts the shared session broker and companion chat so a session started here is visible and steerable from other surfaces. On by default, bound to loopback (127.0.0.1) only.',
|
|
42
46
|
controlPlane: 'Daemon control-plane settings for local admin/API access.',
|
|
@@ -55,9 +59,10 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
55
59
|
surfaces: 'External app surfaces such as Slack, Discord, ntfy, Home Assistant, Telegram, webhooks, chat bridges, and messaging providers.',
|
|
56
60
|
cloudflare: 'Optional Cloudflare control plane, batch queue, Worker, Tunnel, Access, DNS, KV, Durable Objects, Secrets Store, and R2 settings.',
|
|
57
61
|
release: 'Release-channel preference.',
|
|
62
|
+
power: 'Host sleep ownership. Keep-awake holds the host awake (the always-visible "sleep disabled" chip is the safety mechanism — no timer, no AC-only option); automatic inhibition keeps the host awake while real work runs, with an honest hard cap on that work hold.',
|
|
63
|
+
memory: 'Memory provenance surfacing. Whether turns that drew on your standing memories show a small "used N memories" chip with a drill-in listing them. Off by default — when off, nothing is rendered and no context is added.',
|
|
58
64
|
danger: 'High-impact switches for daemon and HTTP listener behavior. These are operational overrides, not normal preferences.',
|
|
59
65
|
tools: 'Tool LLM and helper model routing. Empty provider/model values inherit the active chat route unless a specific helper/tool route is set.',
|
|
60
|
-
flags: 'Advanced feature toggles with no tuning knobs — internal and startup-gated runtime gates. Features that DO have config sit as units (toggle + settings) in their topical category, not here.',
|
|
61
66
|
network: 'Combined network view for daemon control-plane, HTTP listener, browser web surface, and general outbound network settings.',
|
|
62
67
|
fetch: 'Fetch response sanitization and host trust tiers for the fetch tool: sanitize mode plus default trusted/blocked host lists. Gated by the Fetch Response Sanitization feature.',
|
|
63
68
|
agents: 'Sub-agent context-window awareness and per-turn passive knowledge/code injection: token budget, relevance floor, code-chunk limit, and the compaction threshold. Gated by the agent context/injection features.',
|
|
@@ -65,7 +70,7 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
65
70
|
integrations: 'Integration delivery reliability (Slack/Discord/webhook): retry counts, backoff bounds, dead-letter queue size, and SLO enforcement. Gated by the Integration Delivery SLO feature.',
|
|
66
71
|
policy: 'Policy-as-code bundle loading: where the startup policy bundle is loaded from and its file path. Gated by the Policy-as-Code feature.',
|
|
67
72
|
notifications: 'Notification router burst-suppression tuning: burst window, threshold, and cooldown. Gated by the Adaptive Notification Suppression feature.',
|
|
68
|
-
relay: 'Outbound zero-knowledge relay reachability, for reaching this daemon from outside the LAN without opening an inbound port. Off by default
|
|
73
|
+
relay: 'Outbound zero-knowledge relay reachability, for reaching this daemon from outside the LAN without opening an inbound port. Off by default — relay.enabled is the switch. The relay operator sees only ciphertext and connection metadata — self-host your own relay for full control.',
|
|
69
74
|
learning: 'Idle-time memory consolidation: merging duplicate standing memory records and decaying/archiving stale ones. Off by default — nothing runs until enabled.',
|
|
70
75
|
};
|
|
71
76
|
|
|
@@ -181,6 +186,12 @@ function buildSettingContext(modal: SettingsModal, entry: SettingEntry): string[
|
|
|
181
186
|
if (entry.locked) lines.push(`Locked: ${entry.lockReason ?? 'This setting is locked by a higher-priority layer.'}`);
|
|
182
187
|
if (entry.conflict) lines.push(`Conflict: resolve with /settings-sync resolve ${entry.setting.key} local|synced.`);
|
|
183
188
|
|
|
189
|
+
// A settings sub-row owned by a feature unit names its feature so "what does this do" is answerable without scrolling back to the header row.
|
|
190
|
+
if (entry.ownerFlagId) {
|
|
191
|
+
const owner = FEATURE_SETTINGS_BY_ID.get(entry.ownerFlagId);
|
|
192
|
+
if (owner) lines.push(`Part of feature: ${owner.name} (the header row above).`);
|
|
193
|
+
}
|
|
194
|
+
|
|
184
195
|
lines.push('', entry.setting.description);
|
|
185
196
|
|
|
186
197
|
if (
|
|
@@ -225,26 +236,86 @@ function buildSettingContext(modal: SettingsModal, entry: SettingEntry): string[
|
|
|
225
236
|
return lines;
|
|
226
237
|
}
|
|
227
238
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
/**
|
|
240
|
+
* The option shape of a feature header, rendered from the same schema the
|
|
241
|
+
* write path uses: enum headers list every mode choice (marking the current
|
|
242
|
+
* value and which values keep the feature active), boolean headers state the
|
|
243
|
+
* two positions.
|
|
244
|
+
*/
|
|
245
|
+
function buildFeatureOptionLines(entry: SettingEntry): string[] {
|
|
246
|
+
const feature = entry.flag!.feature;
|
|
247
|
+
const setting = entry.setting;
|
|
248
|
+
const lines: string[] = [];
|
|
249
|
+
if (setting.type === 'enum' && setting.enumValues) {
|
|
250
|
+
lines.push('', `Mode choices for ${setting.key}:`);
|
|
251
|
+
const activeValues = feature.enablement.enabledValues ?? [];
|
|
252
|
+
const descriptions = ENUM_VALUE_DESCRIPTIONS[setting.key] ?? {};
|
|
253
|
+
for (const value of setting.enumValues) {
|
|
254
|
+
const marks: string[] = [];
|
|
255
|
+
if (value === String(entry.currentValue)) marks.push('current');
|
|
256
|
+
if (feature.enablement.kind === 'enum') {
|
|
257
|
+
marks.push(activeValues.includes(value) ? 'feature on' : 'feature off');
|
|
258
|
+
}
|
|
259
|
+
const suffix = marks.length > 0 ? ` (${marks.join(', ')})` : '';
|
|
260
|
+
lines.push(`${value}${suffix}: ${descriptions[value] ?? `Use ${value} for this setting.`}`);
|
|
261
|
+
}
|
|
262
|
+
} else if (setting.type === 'boolean') {
|
|
263
|
+
lines.push('');
|
|
264
|
+
lines.push('Possible values:');
|
|
265
|
+
lines.push('true: the feature is enabled.');
|
|
266
|
+
lines.push('false: the feature is disabled.');
|
|
267
|
+
}
|
|
268
|
+
return lines;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Under-cursor documentation for a feature-unit header, rendered entirely
|
|
273
|
+
* from the SDK's per-feature settings metadata: full behavior description,
|
|
274
|
+
* the real option shape, every settings key that tunes the feature, and the
|
|
275
|
+
* honest live/restart state from the gate manager.
|
|
276
|
+
*/
|
|
277
|
+
function buildFlagContext(entry: SettingEntry | null): string[] {
|
|
278
|
+
const flagEntry = entry?.flag ?? null;
|
|
279
|
+
if (!entry || !flagEntry) return ['Features', 'No feature is selected.'];
|
|
280
|
+
const { feature, flag, state, persistedState, pendingRestart } = flagEntry;
|
|
281
|
+
const configOn = isFeatureValueEnabled(feature, entry.currentValue);
|
|
282
|
+
const displayState = state === 'killed' ? 'killed' : configOn ? 'enabled' : 'disabled';
|
|
283
|
+
const lines: string[] = [
|
|
284
|
+
feature.name,
|
|
285
|
+
`Feature: ${feature.id} (${feature.domain} domain)`,
|
|
286
|
+
`Setting: ${feature.enablement.key} = ${formatValue(entry)}`,
|
|
287
|
+
`State: ${displayState}`,
|
|
288
|
+
`Default: ${feature.defaultEnabled ? 'enabled' : 'disabled'}`,
|
|
289
|
+
`Applies: ${feature.restartRequired ? 'on next launch (startup-gated)' : 'immediately'}`,
|
|
290
|
+
...(pendingRestart
|
|
291
|
+
? [`Pending restart: saved as ${persistedState}; effective state stays ${state} until the next launch.`]
|
|
239
292
|
: []),
|
|
240
293
|
'',
|
|
241
|
-
|
|
242
|
-
...(entry.state === 'killed' && entry.flag.killReason ? ['', `Kill reason: ${entry.flag.killReason}`] : []),
|
|
243
|
-
'',
|
|
244
|
-
entry.flag.runtimeToggleable
|
|
245
|
-
? 'Impact: changes apply immediately and are also persisted as an override when they differ from the default.'
|
|
246
|
-
: 'Impact: this flag is persisted as an override and requires restart before startup-only code sees the new state.',
|
|
294
|
+
feature.description,
|
|
247
295
|
];
|
|
296
|
+
|
|
297
|
+
lines.push(...buildFeatureOptionLines(entry));
|
|
298
|
+
|
|
299
|
+
lines.push('');
|
|
300
|
+
if (feature.enablement.kind === 'enum') {
|
|
301
|
+
lines.push(`How it turns on: active while ${feature.enablement.key} is ${(feature.enablement.enabledValues ?? []).join(' or ')}.`);
|
|
302
|
+
} else if (feature.enablement.kind === 'constant' && entry.setting.type !== 'boolean') {
|
|
303
|
+
lines.push('How it turns on: always active; the settings listed below tune its behavior directly.');
|
|
304
|
+
} else {
|
|
305
|
+
lines.push(`How it turns on: ${feature.enablement.key} set to true.`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (feature.settings.length > 1) {
|
|
309
|
+
lines.push('', 'Settings in this feature:');
|
|
310
|
+
for (const key of feature.settings) {
|
|
311
|
+
lines.push(key === feature.enablement.key ? `${key} (this row)` : key);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (state === 'killed' && flag.killReason) {
|
|
316
|
+
lines.push('', `Kill reason: ${flag.killReason}`);
|
|
317
|
+
}
|
|
318
|
+
return lines;
|
|
248
319
|
}
|
|
249
320
|
|
|
250
321
|
function buildMcpContext(modal: SettingsModal, entry: McpEntry | null): string[] {
|
|
@@ -332,9 +403,10 @@ function buildContextLines(modal: SettingsModal, width: number): string[] {
|
|
|
332
403
|
lines.push(...buildSubscriptionContext(modal, modal.getSelectedSubscription()));
|
|
333
404
|
} else {
|
|
334
405
|
const selected = modal.getSelected();
|
|
335
|
-
// A feature-unit
|
|
336
|
-
//
|
|
337
|
-
|
|
406
|
+
// A feature-unit header shows the feature's documentation (full
|
|
407
|
+
// description, option shape, settings list, live/restart state); its
|
|
408
|
+
// settings sub-rows and plain settings show the setting context.
|
|
409
|
+
if (selected?.flag) lines.push(...buildFlagContext(selected));
|
|
338
410
|
else if (selected) lines.push(...buildSettingContext(modal, selected));
|
|
339
411
|
else lines.push('No setting is selected in this category.');
|
|
340
412
|
}
|
|
@@ -355,7 +427,6 @@ function buildContextLines(modal: SettingsModal, width: number): string[] {
|
|
|
355
427
|
function categoryItemCount(modal: SettingsModal, category: SettingsCategory): number {
|
|
356
428
|
if (category === 'mcp') return modal.mcpEntries.length;
|
|
357
429
|
if (category === 'subscriptions') return modal.subscriptionEntries.length;
|
|
358
|
-
// 'flags' (Advanced Features) now flows through the normal group path.
|
|
359
430
|
return modal.groups.get(category)?.length ?? 0;
|
|
360
431
|
}
|
|
361
432
|
|
|
@@ -454,15 +525,18 @@ function renderSettingTableRow(
|
|
|
454
525
|
defaultWidth: number,
|
|
455
526
|
): string {
|
|
456
527
|
if (entry.flag) {
|
|
457
|
-
const {
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
//
|
|
463
|
-
// the
|
|
464
|
-
|
|
465
|
-
|
|
528
|
+
const { feature, state, pendingRestart } = entry.flag;
|
|
529
|
+
const configOn = isFeatureValueEnabled(feature, entry.currentValue);
|
|
530
|
+
const stateMark = state === 'killed' ? '✕' : configOn ? '●' : '○';
|
|
531
|
+
const label = `${stateMark} ${feature.name}`;
|
|
532
|
+
const source = feature.restartRequired ? 'restart' : 'live';
|
|
533
|
+
// A startup-gated feature changed this session shows its saved value with
|
|
534
|
+
// a compact restart-pending marker (⟳), so the row never implies the
|
|
535
|
+
// change already took effect; the documentation pane spells out the full
|
|
536
|
+
// "Pending restart: ..." sentence.
|
|
537
|
+
const rawValue = currentSettingValue(modal, entry, selected);
|
|
538
|
+
const value = pendingRestart ? `${rawValue} ⟳` : rawValue;
|
|
539
|
+
return `${marker} ${padDisplay(label, keyWidth)} ${padDisplay(value, valueWidth)} ${padDisplay('feature', typeWidth)} ${padDisplay(source, sourceWidth)} ${padDisplay(feature.defaultEnabled ? 'enabled' : 'disabled', defaultWidth)}`;
|
|
466
540
|
}
|
|
467
541
|
const value = currentSettingValue(modal, entry, selected);
|
|
468
542
|
const source = `${entry.effectiveSource ?? 'default'}${entry.locked ? ' locked' : ''}${entry.conflict ? ' conflict' : ''}`;
|
|
@@ -626,9 +700,9 @@ function footerText(modal: SettingsModal, width: number): string {
|
|
|
626
700
|
{ key: 'Enter', verb: 'Edit trust' },
|
|
627
701
|
{ key: 'Esc', verb: 'Close' },
|
|
628
702
|
]));
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
//
|
|
703
|
+
// Every topical category that hosts feature units flows through the default
|
|
704
|
+
// settings footer below — Enter/Space toggles a feature-unit header exactly
|
|
705
|
+
// as it edits/toggles any setting row.
|
|
632
706
|
// Default settings pane: tier the reset affordances by available width.
|
|
633
707
|
// W<80: minimal — only the most critical action survives.
|
|
634
708
|
// W<160: compact but still shows both reset affordances.
|
|
@@ -639,6 +713,7 @@ function footerText(modal: SettingsModal, width: number): string {
|
|
|
639
713
|
return formatHints([
|
|
640
714
|
{ key: 'Up/Down', verb: 'Move' },
|
|
641
715
|
{ key: 'Enter/Space', verb: 'Edit' },
|
|
716
|
+
{ key: 'PgUp/PgDn', verb: 'docs' },
|
|
642
717
|
{ key: '⇧R', verb: 'reset cat' },
|
|
643
718
|
{ key: '^⇧R', verb: 'reset all' },
|
|
644
719
|
{ key: 'Esc', verb: 'Close' },
|
|
@@ -647,6 +722,7 @@ function footerText(modal: SettingsModal, width: number): string {
|
|
|
647
722
|
{ key: 'Up/Down', verb: 'Setting' },
|
|
648
723
|
{ key: 'Left', verb: 'Categories' },
|
|
649
724
|
{ key: 'Enter/Space', verb: 'Edit/toggle' },
|
|
725
|
+
{ key: 'PgUp/PgDn', verb: 'Scroll docs' },
|
|
650
726
|
{ key: '⇧R', verb: 'reset cat' },
|
|
651
727
|
{ key: '^⇧R', verb: 'reset all' },
|
|
652
728
|
{ key: 'Esc', verb: 'Close' },
|
|
@@ -664,12 +740,26 @@ export function renderSettingsModal(
|
|
|
664
740
|
];
|
|
665
741
|
const metrics = getFullscreenWorkspaceMetrics({ width, height: viewportHeight });
|
|
666
742
|
const categoryRows = renderCategories(modal, metrics.leftWidth - 2, metrics.bodyRows);
|
|
667
|
-
|
|
743
|
+
// Documentation pane: long content SCROLLS (PgUp/PgDn) with honest
|
|
744
|
+
// more-above/below markers — it is never silently clipped.
|
|
745
|
+
const allContextLines = buildContextLines(modal, metrics.contextWidth);
|
|
746
|
+
const visibleContext = metrics.contextRows;
|
|
747
|
+
const maxContextScroll = Math.max(0, allContextLines.length - visibleContext);
|
|
748
|
+
const contextOffset = Math.min(Math.max(0, modal.contextScroll ?? 0), maxContextScroll);
|
|
749
|
+
const windowedContext = allContextLines.slice(contextOffset, contextOffset + visibleContext);
|
|
750
|
+
if (contextOffset > 0) {
|
|
751
|
+
windowedContext[0] = `${GLYPHS.navigation.moreAbove} ${contextOffset} more line(s) above — PgUp`;
|
|
752
|
+
}
|
|
753
|
+
if (contextOffset + visibleContext < allContextLines.length) {
|
|
754
|
+
const below = allContextLines.length - contextOffset - visibleContext;
|
|
755
|
+
windowedContext[windowedContext.length - 1] = `${GLYPHS.navigation.moreBelow} ${below} more line(s) below — PgDn`;
|
|
756
|
+
}
|
|
757
|
+
const contextRows = windowedContext.map((text, row): WorkspaceRow => {
|
|
668
758
|
const selectedSetting = modal.getSelected();
|
|
669
|
-
const isTitle = row === 0 || (selectedSetting !== null && text === getSettingLabel(selectedSetting));
|
|
759
|
+
const isTitle = (row === 0 && contextOffset === 0) || (selectedSetting !== null && text === getSettingLabel(selectedSetting));
|
|
670
760
|
return {
|
|
671
761
|
text,
|
|
672
|
-
fg: row === 0 ? PALETTE.title : text.endsWith(':') ? PALETTE.subtitle : PALETTE.text,
|
|
762
|
+
fg: row === 0 && contextOffset === 0 ? PALETTE.title : text.endsWith(':') ? PALETTE.subtitle : PALETTE.text,
|
|
673
763
|
bold: isTitle,
|
|
674
764
|
dim: text.length === 0,
|
|
675
765
|
};
|
|
@@ -65,6 +65,10 @@ export interface ShellFooterBuildOptions {
|
|
|
65
65
|
* embedded/local mode so no segment renders.
|
|
66
66
|
*/
|
|
67
67
|
readonly sessionSpineStatus?: 'online' | 'offline';
|
|
68
|
+
/** The web surface's reachable URL, when that surface is enabled; a persistent footer segment. */
|
|
69
|
+
readonly webSurfaceUrl?: string;
|
|
70
|
+
/** True while power.keepAwake holds — renders the always-visible "sleep disabled" chip. */
|
|
71
|
+
readonly powerKeepAwake?: boolean;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
export interface ShellFooterBuildResult {
|
|
@@ -144,6 +148,8 @@ export function buildShellFooter(
|
|
|
144
148
|
options.compact ?? false,
|
|
145
149
|
options.sessionSpineStatus,
|
|
146
150
|
options.permissionMode,
|
|
151
|
+
options.webSurfaceUrl,
|
|
152
|
+
options.powerKeepAwake,
|
|
147
153
|
);
|
|
148
154
|
// Compact posture drops the process indicator and context hint entirely so
|
|
149
155
|
// the footer fits within ~5 rows on short terminals.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* terminal-bg-probe — OSC 11 terminal-background detection for `auto` theme mode.
|
|
3
3
|
*
|
|
4
|
-
|
|
4
|
+
*. On startup, when appearance is `auto` and stdout is a TTY, we ask
|
|
5
5
|
* the terminal for its background colour with an OSC 11 query and classify the
|
|
6
6
|
* reply as light or dark. Everything about this is best-effort and conservative:
|
|
7
7
|
* any timeout, any unparseable reply, any ambiguity resolves to DARK, which is
|
package/src/renderer/theme.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* DEFAULT_PANEL_PALETTE, DEFAULT_STYLE, FULLSCREEN_PALETTE and
|
|
22
22
|
* DEFAULT_OVERLAY_PALETTE all read through ONE mode-resolved path instead of
|
|
23
23
|
* importing the static UI_TONES constant directly. Mode stays 'dark'
|
|
24
|
-
* everywhere until the terminal-bg-probe lands (see
|
|
24
|
+
* everywhere until the terminal-bg-probe lands (see) — the light
|
|
25
25
|
* entry is type-complete, not a shipped light theme.
|
|
26
26
|
*/
|
|
27
27
|
|
|
@@ -173,7 +173,7 @@ export const DARK_THEME: Readonly<ThemeTokens> = DARK;
|
|
|
173
173
|
//
|
|
174
174
|
// UI_TONES (ui-primitives.ts) is the dark entry. The light entry mirrors
|
|
175
175
|
// dark for every role that has no light-appropriate equivalent yet — the
|
|
176
|
-
//
|
|
176
|
+
// deliverable is the mode dimension and single read path, not a
|
|
177
177
|
// shipped light chrome theme (see module doc comment above).
|
|
178
178
|
// ---------------------------------------------------------------------------
|
|
179
179
|
|
|
@@ -242,7 +242,7 @@ export function resolveUiTones(mode: ThemeMode): Readonly<UiToneTokens> {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
// ===========================================================================
|
|
245
|
-
// Active-mode runtime (
|
|
245
|
+
// Active-mode runtime (terminal-bg-probe landing).
|
|
246
246
|
//
|
|
247
247
|
// The mode is decided ONCE at startup — from appearance config (display.themeMode
|
|
248
248
|
// forced dark/light) or the terminal-background probe (auto) — and is then stable
|
|
@@ -147,7 +147,7 @@ function extractKeyArg(toolCall: ToolCall): string {
|
|
|
147
147
|
* Layout: [margin] [icon] [space] [tool name padded] [key arg] [summary] [duration]
|
|
148
148
|
*
|
|
149
149
|
* @param toolCall - The tool call being executed
|
|
150
|
-
* @param status - 'executing' | 'done' | 'error'
|
|
150
|
+
* @param status - 'executing' | 'done' | 'error' | 'pending' | 'cancelled'
|
|
151
151
|
* @param resultSummary - Optional brief summary (e.g., "3 files", "exit 0")
|
|
152
152
|
* @param width - Terminal width
|
|
153
153
|
* @param durationMs - Optional duration in milliseconds
|
|
@@ -157,7 +157,7 @@ function extractKeyArg(toolCall: ToolCall): string {
|
|
|
157
157
|
*/
|
|
158
158
|
export function renderToolCallBlock(
|
|
159
159
|
toolCall: ToolCall,
|
|
160
|
-
status: 'executing' | 'done' | 'error' | 'pending',
|
|
160
|
+
status: 'executing' | 'done' | 'error' | 'pending' | 'cancelled',
|
|
161
161
|
resultSummary: string | undefined,
|
|
162
162
|
width: number,
|
|
163
163
|
durationMs?: number,
|
|
@@ -173,13 +173,17 @@ export function renderToolCallBlock(
|
|
|
173
173
|
|
|
174
174
|
// Status icon. 'pending' means the tool is still awaiting a decision
|
|
175
175
|
// (e.g. an approval prompt) and has NOT run yet — it uses the hollow idle
|
|
176
|
-
// glyph so a not-yet-run tool never shows the completed green ✓ (
|
|
176
|
+
// glyph so a not-yet-run tool never shows the completed green ✓ (2c).
|
|
177
|
+
// 'cancelled' means the user stopped THIS call mid-flight — the blocked glyph
|
|
178
|
+
// in the warn tone, distinct from both success and a real error.
|
|
177
179
|
const icon = status === 'done' ? TOOL_STATUS.SUCCESS_ICON
|
|
178
180
|
: status === 'error' ? TOOL_STATUS.FAIL_ICON
|
|
181
|
+
: status === 'cancelled' ? GLYPHS.status.blocked
|
|
179
182
|
: status === 'pending' ? GLYPHS.status.idle
|
|
180
183
|
: TOOL_STATUS.SPINNER_FRAMES[(frameIndex ?? 0) % TOOL_STATUS.SPINNER_FRAMES.length];
|
|
181
184
|
const iconColor = status === 'done' ? t.chrome.good
|
|
182
185
|
: status === 'error' ? t.chrome.bad
|
|
186
|
+
: status === 'cancelled' ? t.chrome.warn
|
|
183
187
|
: '244';
|
|
184
188
|
const rightText = (() => {
|
|
185
189
|
if (durationMs !== undefined && status === 'done') {
|
|
@@ -204,7 +208,7 @@ export function renderToolCallBlock(
|
|
|
204
208
|
let col: number = leftStart;
|
|
205
209
|
|
|
206
210
|
if (col < leftEndExclusive) {
|
|
207
|
-
line[col] = createStyledCell(icon, { fg: iconColor, bold: status === 'done' || status === 'error' });
|
|
211
|
+
line[col] = createStyledCell(icon, { fg: iconColor, bold: status === 'done' || status === 'error' || status === 'cancelled' });
|
|
208
212
|
}
|
|
209
213
|
col += 2; // icon + space
|
|
210
214
|
|
|
@@ -215,9 +219,11 @@ export function renderToolCallBlock(
|
|
|
215
219
|
const keyArg = stripDangerousAnsi(extractKeyArg(toolCall));
|
|
216
220
|
const suffixText = status === 'error' && errorMsg
|
|
217
221
|
? `- ${stripDangerousAnsi(errorMsg).slice(0, 40)}`
|
|
218
|
-
: status === '
|
|
219
|
-
?
|
|
220
|
-
: ''
|
|
222
|
+
: status === 'cancelled'
|
|
223
|
+
? '- cancelled'
|
|
224
|
+
: status === 'done' && resultSummary
|
|
225
|
+
? `(${stripDangerousAnsi(resultSummary)})`
|
|
226
|
+
: '';
|
|
221
227
|
const leftBudget = Math.max(0, leftEndExclusive - col);
|
|
222
228
|
const leftSegments = buildLeftSegments(rawName, keyArg, suffixText, leftBudget);
|
|
223
229
|
for (const segment of leftSegments) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* in conversation-rendering.ts) shows "wrote haiku.txt (532 B)" instead of a
|
|
5
5
|
* raw `{"files_written":1,"bytes_written":532,...}` JSON blob. The full payload
|
|
6
6
|
* stays reachable behind the existing collapse/expand toggle — this only
|
|
7
|
-
* changes the collapsed preview line, never destroys data. (
|
|
7
|
+
* changes the collapsed preview line, never destroys data. (item 3.)
|
|
8
8
|
*
|
|
9
9
|
* Returning `null` means "no honest summary for this shape" — the caller then
|
|
10
10
|
* falls back to the previous raw-first-line preview, so an unrecognised tool or
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-turn knowledge injection record rendering.
|
|
3
3
|
*
|
|
4
|
-
* The SDK's passive per-turn retrieval engine
|
|
5
|
-
* packages/sdk/src/platform/agents/turn-knowledge-injection.ts) stores one
|
|
4
|
+
* The SDK's passive per-turn retrieval engine
|
|
5
|
+
* (packages/sdk/src/platform/agents/turn-knowledge-injection.ts) stores one
|
|
6
6
|
* `TurnInjectionRecord` per turn on `AgentRecord.turnInjections` (a bounded
|
|
7
7
|
* ring, default 20 entries) and appends the same record to the agent's
|
|
8
8
|
* session transcript as `{type:'knowledge_injection', turn, ...record}`.
|
|
@@ -34,10 +34,11 @@ function fmtN(n: number): string {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Annotate each injected id with its source
|
|
37
|
+
* Annotate each injected id with its source. Code-index hits get a
|
|
38
38
|
* ` [code]` tag; memory records are left bare so a memory-only line renders
|
|
39
|
-
* byte-identically to the
|
|
40
|
-
* `injectedIds`; a missing/short entry
|
|
39
|
+
* byte-identically to the output from before code-index hits existed.
|
|
40
|
+
* `injectedSources` is parallel to `injectedIds`; a missing/short entry
|
|
41
|
+
* defaults to memory (no tag).
|
|
41
42
|
*/
|
|
42
43
|
function labelInjectedIds(entry: TurnInjectionEntry): string {
|
|
43
44
|
const sources = entry.injectedSources ?? [];
|
|
@@ -107,7 +108,7 @@ export function buildTurnInjectionsText(agentId: string, entries: readonly TurnI
|
|
|
107
108
|
|
|
108
109
|
/**
|
|
109
110
|
* Render the MAIN interactive session's per-turn injection ring
|
|
110
|
-
* (`Orchestrator.getTurnInjections()
|
|
111
|
+
* (`Orchestrator.getTurnInjections()`), most-recent-turn first, with the
|
|
111
112
|
* same honest empty state as the per-agent path. The main-session counterpart
|
|
112
113
|
* to {@link buildTurnInjectionsText}: no agent id, main-session-appropriate
|
|
113
114
|
* wording, but the identical per-entry rendering (`formatTurnInjectionEntry`).
|
|
@@ -10,6 +10,8 @@ import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
|
10
10
|
import { abbreviateCount } from '../utils/format-number.ts';
|
|
11
11
|
import { computeContextUsage } from '../core/context-usage.ts';
|
|
12
12
|
import { permissionModeLabel, permissionModeTone } from '../core/permission-mode.ts';
|
|
13
|
+
import { SLEEP_DISABLED_CHIP } from '../core/power-status.ts';
|
|
14
|
+
import { renderQueuedMessageList, renderMemoryProvenanceChip, type MemoryProvenanceEntry } from './composer-fragments.ts';
|
|
13
15
|
import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
|
|
14
16
|
import { buildFooterTip, isAgentActive } from './footer-tips.ts';
|
|
15
17
|
import type { StreamMetrics } from '../core/stream-event-wiring.ts';
|
|
@@ -173,6 +175,16 @@ export class UIFactory {
|
|
|
173
175
|
});
|
|
174
176
|
}
|
|
175
177
|
|
|
178
|
+
/** The mid-turn queue as an editable list — see composer-fragments.ts. */
|
|
179
|
+
public static createQueuedMessageList(width: number, items: readonly { readonly id: string; readonly text: string }[]): Line[] {
|
|
180
|
+
return renderQueuedMessageList(width, items);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The optional "used N memories" provenance chip — see composer-fragments.ts. */
|
|
184
|
+
public static createMemoryProvenanceChip(width: number, count: number, entries: readonly MemoryProvenanceEntry[], expanded: boolean): Line[] {
|
|
185
|
+
return renderMemoryProvenanceChip(width, count, entries, expanded);
|
|
186
|
+
}
|
|
187
|
+
|
|
176
188
|
public static createFooter(
|
|
177
189
|
width: number,
|
|
178
190
|
prompt: string,
|
|
@@ -202,6 +214,13 @@ export class UIFactory {
|
|
|
202
214
|
// Session permission mode (config value). Rendered as a leading pill in the
|
|
203
215
|
// posture block; undefined suppresses the pill (e.g. bare test callers).
|
|
204
216
|
permissionMode?: string,
|
|
217
|
+
// The web surface's reachable URL, when that surface is enabled — a
|
|
218
|
+
// persistent low-priority context segment; undefined suppresses it.
|
|
219
|
+
webSurfaceUrl?: string,
|
|
220
|
+
// True while power.keepAwake holds — renders the always-visible
|
|
221
|
+
// "sleep disabled" chip in the posture block (the danger-mode idiom: a
|
|
222
|
+
// persistent, colored surface indicator, the safety mechanism itself).
|
|
223
|
+
powerKeepAwake?: boolean,
|
|
205
224
|
): Line[] {
|
|
206
225
|
const lines: Line[] = [];
|
|
207
226
|
const promptLines = prompt.split('\n');
|
|
@@ -265,7 +284,7 @@ export class UIFactory {
|
|
|
265
284
|
}
|
|
266
285
|
}
|
|
267
286
|
|
|
268
|
-
//
|
|
287
|
+
// focus visibility (item 1c): when the panel workspace owns focus
|
|
269
288
|
// and the composer is empty, name the state and the way back — the
|
|
270
289
|
// dimmed prompt alone (TEXT_COLOR/BG_COLOR above) told you nothing was
|
|
271
290
|
// wrong, but not why keystrokes weren't landing here.
|
|
@@ -365,6 +384,10 @@ export class UIFactory {
|
|
|
365
384
|
: t.chrome.warn;
|
|
366
385
|
composerTokens.push({ text: ` risk:${composerPendingRisk} `, fg: riskColor, bold: true });
|
|
367
386
|
}
|
|
387
|
+
// "sleep disabled" chip — always visible while keep-awake holds, in the
|
|
388
|
+
// danger-mode idiom (a persistent warn-tone indicator; the chip is the
|
|
389
|
+
// safety mechanism, so there is no timer and no AC-only variant).
|
|
390
|
+
if (powerKeepAwake) composerTokens.push({ text: ` ${SLEEP_DISABLED_CHIP} `, fg: t.chrome.warn, bold: true });
|
|
368
391
|
if (composerStatus && composerStatus !== 'idle') composerTokens.push({ text: ` state:${composerStatus} `, fg: '244', dim: true });
|
|
369
392
|
if (composerFlags && composerFlags.length > 0) composerTokens.push({ text: ` flags:${composerFlags.join(',')} `, fg: '244', dim: true });
|
|
370
393
|
if (!compact && composerTokens.length > 0) {
|
|
@@ -388,6 +411,18 @@ export class UIFactory {
|
|
|
388
411
|
if (px >= width) break;
|
|
389
412
|
}
|
|
390
413
|
lines.push(postureLine);
|
|
414
|
+
} else if (compact && powerKeepAwake) {
|
|
415
|
+
// The keep-awake chip is a safety indicator — it survives compact height
|
|
416
|
+
// even though the rest of the posture row (mode/ctx/risk/state/flags) is
|
|
417
|
+
// dropped at height < 30. Its own compact-surviving slot.
|
|
418
|
+
const chipLine = createBaseLine();
|
|
419
|
+
let cx = 2;
|
|
420
|
+
for (const ch of ` ${SLEEP_DISABLED_CHIP} `) {
|
|
421
|
+
if (cx >= width) break;
|
|
422
|
+
chipLine[cx] = { char: ch, fg: t.chrome.warn, bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false };
|
|
423
|
+
cx += getDisplayWidth(ch);
|
|
424
|
+
}
|
|
425
|
+
lines.push(chipLine);
|
|
391
426
|
}
|
|
392
427
|
const isRecentlyCopied = Date.now() - lastCopyTime < 2000;
|
|
393
428
|
// Token usage line + running ~$ cost estimate (derived from the usage
|
|
@@ -466,6 +501,8 @@ export class UIFactory {
|
|
|
466
501
|
}
|
|
467
502
|
// Cross-surface spine posture — plain words, no blame. Adopted mode only.
|
|
468
503
|
if (sessionSpineStatus) ctxParts.push({ text: `spine:${sessionSpineStatus}`, priority: 1 });
|
|
504
|
+
// Web surface reachability — plain, persistent; dropped first under width pressure.
|
|
505
|
+
if (webSurfaceUrl) ctxParts.push({ text: `web:${webSurfaceUrl}`, priority: 2 });
|
|
469
506
|
if (toolCount) ctxParts.push({ text: `${toolCount} tools`, priority: 2 });
|
|
470
507
|
// Labeled "notify" (not "hitl") — /mode (aliased /hitl) governs UX
|
|
471
508
|
// notification verbosity (quiet/balanced/operator), not tool
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
|
|
57
57
|
export type CreateBootstrapCommandContextOptions = {
|
|
58
58
|
configManager: ConfigManager;
|
|
59
|
+
pairingTokens?: import('@pellux/goodvibes-sdk/platform/pairing').PairingTokenManager;
|
|
59
60
|
featureFlagManager?: import('@/runtime/index.ts').FeatureFlagManager;
|
|
60
61
|
providerRegistry: ProviderRegistry;
|
|
61
62
|
conversation: ConversationManager;
|
|
@@ -67,7 +68,7 @@ export type CreateBootstrapCommandContextOptions = {
|
|
|
67
68
|
mcpRegistry: McpRegistry;
|
|
68
69
|
voiceProviderRegistry?: VoiceProviderRegistry;
|
|
69
70
|
voiceService?: VoiceService;
|
|
70
|
-
/**
|
|
71
|
+
/** Direct-command consumers (`/search`, `/image`) of already-constructed RuntimeServices. */
|
|
71
72
|
webSearchService?: import('@pellux/goodvibes-sdk/platform/web-search').WebSearchService;
|
|
72
73
|
mediaProviders?: import('@pellux/goodvibes-sdk/platform/media').MediaProviderRegistry;
|
|
73
74
|
artifactStore?: import('@pellux/goodvibes-sdk/platform/artifacts').ArtifactStore;
|
|
@@ -113,6 +114,7 @@ export type CreateBootstrapCommandContextOptions = {
|
|
|
113
114
|
adaptivePlanner?: AdaptivePlanner;
|
|
114
115
|
sessionOrchestration?: ShellSessionOrchestrationService;
|
|
115
116
|
operatorClient?: OperatorClient;
|
|
117
|
+
userPermissionRuleStore?: import('@pellux/goodvibes-sdk/platform/permissions').UserPermissionRuleStore;
|
|
116
118
|
peerClient?: PeerClient;
|
|
117
119
|
knowledgeApi?: KnowledgeApi;
|
|
118
120
|
memorySpine?: MemorySpineClient;
|
|
@@ -143,6 +145,7 @@ export function createBootstrapCommandContext(
|
|
|
143
145
|
const {
|
|
144
146
|
providerRegistry,
|
|
145
147
|
configManager,
|
|
148
|
+
pairingTokens,
|
|
146
149
|
featureFlagManager,
|
|
147
150
|
conversation,
|
|
148
151
|
runtime,
|
|
@@ -206,6 +209,7 @@ export function createBootstrapCommandContext(
|
|
|
206
209
|
adaptivePlanner,
|
|
207
210
|
sessionOrchestration,
|
|
208
211
|
operatorClient,
|
|
212
|
+
userPermissionRuleStore,
|
|
209
213
|
peerClient,
|
|
210
214
|
knowledgeApi,
|
|
211
215
|
memorySpine,
|
|
@@ -286,13 +290,14 @@ export function createBootstrapCommandContext(
|
|
|
286
290
|
projectPlanningProjectId,
|
|
287
291
|
workPlanStore,
|
|
288
292
|
}, shellServices);
|
|
289
|
-
const platform = createBootstrapCommandPlatformSection({ configManager, featureFlagManager, voiceProviderRegistry, voiceService, webSearchService, mediaProviders, artifactStore }, shellServices);
|
|
293
|
+
const platform = createBootstrapCommandPlatformSection({ configManager, pairingTokens, featureFlagManager, voiceProviderRegistry, voiceService, webSearchService, mediaProviders, artifactStore }, shellServices);
|
|
290
294
|
const extensions = createBootstrapCommandExtensionsSection({
|
|
291
295
|
toolRegistry,
|
|
292
296
|
mcpRegistry,
|
|
293
297
|
}, shellServices);
|
|
294
298
|
const clients = createBootstrapCommandClientsSection({
|
|
295
299
|
operatorClient,
|
|
300
|
+
userPermissionRuleStore,
|
|
296
301
|
peerClient,
|
|
297
302
|
providerApi,
|
|
298
303
|
knowledgeApi,
|