@pellux/goodvibes-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.goodvibes/GOODVIBES.md +35 -0
- package/.goodvibes/agents/reviewer.md +48 -0
- package/.goodvibes/skills/add-provider/SKILL.md +199 -0
- package/CHANGELOG.md +25 -0
- package/README.md +74 -0
- package/bin/goodvibes-agent.ts +2 -0
- package/docs/README.md +23 -0
- package/docs/deployment-and-services.md +57 -0
- package/docs/getting-started.md +53 -0
- package/docs/release-and-publishing.md +46 -0
- package/package.json +134 -0
- package/scripts/check-bun.sh +20 -0
- package/src/audio/player.ts +156 -0
- package/src/audio/spoken-turn-controller.ts +203 -0
- package/src/audio/spoken-turn-model-routing.ts +117 -0
- package/src/audio/spoken-turn-wiring.ts +44 -0
- package/src/audio/text-chunker.ts +110 -0
- package/src/cli/bundle-command.ts +227 -0
- package/src/cli/completion.ts +90 -0
- package/src/cli/config-overrides.ts +159 -0
- package/src/cli/endpoints.ts +63 -0
- package/src/cli/entrypoint.ts +172 -0
- package/src/cli/help.ts +299 -0
- package/src/cli/index.ts +11 -0
- package/src/cli/management-commands.ts +426 -0
- package/src/cli/management.ts +744 -0
- package/src/cli/network-posture.ts +46 -0
- package/src/cli/package-verification.ts +123 -0
- package/src/cli/parser.ts +369 -0
- package/src/cli/provider-auth-routes.ts +22 -0
- package/src/cli/provider-classification.ts +107 -0
- package/src/cli/redaction.ts +105 -0
- package/src/cli/service-command.ts +26 -0
- package/src/cli/service-posture.ts +482 -0
- package/src/cli/status.ts +383 -0
- package/src/cli/surface-command.ts +247 -0
- package/src/cli/tui-startup.ts +32 -0
- package/src/cli/types.ts +69 -0
- package/src/cli-flags.ts +21 -0
- package/src/config/goodvibes-home-audit.ts +465 -0
- package/src/config/index.ts +57 -0
- package/src/config/provider-model.ts +23 -0
- package/src/config/secret-config.ts +119 -0
- package/src/config/secrets.ts +71 -0
- package/src/config/surface.ts +1 -0
- package/src/core/composer-state.ts +61 -0
- package/src/core/conversation-rendering.ts +359 -0
- package/src/core/conversation.ts +551 -0
- package/src/core/history.ts +45 -0
- package/src/core/orchestrator.ts +7 -0
- package/src/core/system-message-router.ts +171 -0
- package/src/daemon/cli.ts +55 -0
- package/src/daemon/safe-serve.ts +61 -0
- package/src/input/agent-workspace.ts +428 -0
- package/src/input/autocomplete.ts +96 -0
- package/src/input/bookmark-modal.ts +115 -0
- package/src/input/command-args-hint.ts +36 -0
- package/src/input/command-registry.ts +329 -0
- package/src/input/commands/agent-externalized-tui.ts +73 -0
- package/src/input/commands/agent-workspace-runtime.ts +17 -0
- package/src/input/commands/branch-runtime.ts +72 -0
- package/src/input/commands/cloudflare-runtime.ts +370 -0
- package/src/input/commands/config.ts +18 -0
- package/src/input/commands/control-room-runtime.ts +255 -0
- package/src/input/commands/conversation-runtime.ts +207 -0
- package/src/input/commands/discovery-runtime.ts +52 -0
- package/src/input/commands/eval.ts +204 -0
- package/src/input/commands/experience-runtime.ts +278 -0
- package/src/input/commands/guidance-runtime.ts +106 -0
- package/src/input/commands/health-runtime.ts +434 -0
- package/src/input/commands/hooks-runtime.ts +148 -0
- package/src/input/commands/incident-runtime.ts +95 -0
- package/src/input/commands/integration-runtime.ts +394 -0
- package/src/input/commands/intelligence-runtime.ts +223 -0
- package/src/input/commands/knowledge.ts +531 -0
- package/src/input/commands/local-auth-runtime.ts +105 -0
- package/src/input/commands/local-provider-runtime.ts +170 -0
- package/src/input/commands/local-runtime.ts +392 -0
- package/src/input/commands/local-setup-review.ts +199 -0
- package/src/input/commands/local-setup-transfer.ts +135 -0
- package/src/input/commands/local-setup.ts +282 -0
- package/src/input/commands/managed-runtime.ts +209 -0
- package/src/input/commands/marketplace-runtime.ts +290 -0
- package/src/input/commands/mcp-runtime.ts +432 -0
- package/src/input/commands/memory-product-runtime.ts +111 -0
- package/src/input/commands/memory.ts +151 -0
- package/src/input/commands/notify-runtime.ts +83 -0
- package/src/input/commands/onboarding-runtime.ts +14 -0
- package/src/input/commands/operator-panel-runtime.ts +146 -0
- package/src/input/commands/operator-runtime.ts +392 -0
- package/src/input/commands/planning-runtime.ts +205 -0
- package/src/input/commands/platform-access-runtime.ts +422 -0
- package/src/input/commands/platform-services-runtime.ts +246 -0
- package/src/input/commands/policy-dispatch.ts +339 -0
- package/src/input/commands/policy.ts +17 -0
- package/src/input/commands/product-runtime.ts +351 -0
- package/src/input/commands/profile-sync-runtime.ts +99 -0
- package/src/input/commands/provider-accounts-runtime.ts +113 -0
- package/src/input/commands/provider.ts +363 -0
- package/src/input/commands/qrcode-runtime.ts +20 -0
- package/src/input/commands/quit-shared.ts +162 -0
- package/src/input/commands/recall-bundle.ts +132 -0
- package/src/input/commands/recall-capture.ts +152 -0
- package/src/input/commands/recall-query.ts +229 -0
- package/src/input/commands/recall-review.ts +98 -0
- package/src/input/commands/recall-shared.ts +22 -0
- package/src/input/commands/remote-runtime-pool.ts +106 -0
- package/src/input/commands/remote-runtime-setup.ts +199 -0
- package/src/input/commands/remote-runtime.ts +431 -0
- package/src/input/commands/replay-runtime.ts +18 -0
- package/src/input/commands/runtime-services.ts +291 -0
- package/src/input/commands/schedule-runtime.ts +91 -0
- package/src/input/commands/services-runtime.ts +209 -0
- package/src/input/commands/session-content.ts +408 -0
- package/src/input/commands/session-workflow.ts +464 -0
- package/src/input/commands/session.ts +375 -0
- package/src/input/commands/settings-sync-runtime.ts +174 -0
- package/src/input/commands/share-runtime.ts +119 -0
- package/src/input/commands/shell-core.ts +307 -0
- package/src/input/commands/skills-runtime.ts +221 -0
- package/src/input/commands/subscription-runtime.ts +434 -0
- package/src/input/commands/tasks-runtime.ts +230 -0
- package/src/input/commands/teamwork-runtime.ts +339 -0
- package/src/input/commands/teleport-runtime.ts +57 -0
- package/src/input/commands/tts-runtime.ts +29 -0
- package/src/input/commands/work-plan-runtime.ts +169 -0
- package/src/input/commands.ts +131 -0
- package/src/input/feed-context-factory.ts +254 -0
- package/src/input/file-picker.ts +192 -0
- package/src/input/handler-command-route.ts +180 -0
- package/src/input/handler-content-actions.ts +497 -0
- package/src/input/handler-feed-routes.ts +648 -0
- package/src/input/handler-feed.ts +452 -0
- package/src/input/handler-interactions.ts +281 -0
- package/src/input/handler-modal-routes.ts +418 -0
- package/src/input/handler-modal-stack.ts +263 -0
- package/src/input/handler-modal-token-routes.ts +329 -0
- package/src/input/handler-onboarding-cloudflare.ts +391 -0
- package/src/input/handler-onboarding.ts +620 -0
- package/src/input/handler-picker-routes.ts +472 -0
- package/src/input/handler-prompt-buffer.ts +320 -0
- package/src/input/handler-shortcuts.ts +213 -0
- package/src/input/handler-ui-state.ts +372 -0
- package/src/input/handler.ts +729 -0
- package/src/input/input-history.ts +297 -0
- package/src/input/keybindings.ts +292 -0
- package/src/input/mcp-workspace.ts +554 -0
- package/src/input/model-picker-provider-filter.ts +28 -0
- package/src/input/model-picker-types.ts +137 -0
- package/src/input/model-picker.ts +797 -0
- package/src/input/onboarding/handler-onboarding-routes.ts +125 -0
- package/src/input/onboarding/onboarding-runtime-status.ts +87 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +277 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +494 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +204 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +158 -0
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +130 -0
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +762 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +167 -0
- package/src/input/onboarding/onboarding-wizard-rules.ts +256 -0
- package/src/input/onboarding/onboarding-wizard-state.ts +365 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +798 -0
- package/src/input/onboarding/onboarding-wizard-types.ts +195 -0
- package/src/input/onboarding/onboarding-wizard.ts +711 -0
- package/src/input/panel-integration-actions.ts +78 -0
- package/src/input/profile-picker-modal.ts +222 -0
- package/src/input/search.ts +100 -0
- package/src/input/selection-modal.ts +163 -0
- package/src/input/selection.ts +135 -0
- package/src/input/session-picker-modal.ts +136 -0
- package/src/input/settings-modal-behavior.ts +37 -0
- package/src/input/settings-modal-secrets.ts +41 -0
- package/src/input/settings-modal-subscriptions.ts +95 -0
- package/src/input/settings-modal-types.ts +91 -0
- package/src/input/settings-modal.ts +793 -0
- package/src/input/submission-intent.ts +17 -0
- package/src/input/submission-router.ts +59 -0
- package/src/input/tts-settings-actions.ts +100 -0
- package/src/main.ts +792 -0
- package/src/mcp/runtime-reload.ts +81 -0
- package/src/panels/agent-inspector-panel.ts +521 -0
- package/src/panels/agent-inspector-shared.ts +94 -0
- package/src/panels/agent-logs-panel.ts +559 -0
- package/src/panels/agent-logs-shared.ts +129 -0
- package/src/panels/approval-panel.ts +150 -0
- package/src/panels/automation-control-panel.ts +212 -0
- package/src/panels/base-panel.ts +254 -0
- package/src/panels/builtin/agent.ts +117 -0
- package/src/panels/builtin/development.ts +31 -0
- package/src/panels/builtin/knowledge.ts +26 -0
- package/src/panels/builtin/operations.ts +349 -0
- package/src/panels/builtin/session.ts +129 -0
- package/src/panels/builtin/shared.ts +274 -0
- package/src/panels/builtin-panels.ts +23 -0
- package/src/panels/cockpit-panel.ts +183 -0
- package/src/panels/communication-panel.ts +153 -0
- package/src/panels/confirm-state.ts +61 -0
- package/src/panels/context-visualizer-panel.ts +204 -0
- package/src/panels/control-plane-panel.ts +211 -0
- package/src/panels/cost-tracker-panel.ts +444 -0
- package/src/panels/debug-panel.ts +432 -0
- package/src/panels/diff-panel.ts +520 -0
- package/src/panels/docs-panel.ts +283 -0
- package/src/panels/eval-panel.ts +399 -0
- package/src/panels/file-explorer-panel.ts +584 -0
- package/src/panels/file-preview-panel.ts +434 -0
- package/src/panels/forensics-panel.ts +364 -0
- package/src/panels/git-panel.ts +638 -0
- package/src/panels/hooks-panel.ts +239 -0
- package/src/panels/incident-review-panel.ts +197 -0
- package/src/panels/index.ts +46 -0
- package/src/panels/intelligence-panel.ts +176 -0
- package/src/panels/knowledge-panel.ts +345 -0
- package/src/panels/local-auth-panel.ts +130 -0
- package/src/panels/marketplace-panel.ts +212 -0
- package/src/panels/memory-panel.ts +225 -0
- package/src/panels/ops-control-panel.ts +150 -0
- package/src/panels/ops-strategy-panel.ts +235 -0
- package/src/panels/orchestration-panel.ts +273 -0
- package/src/panels/panel-list-panel.ts +509 -0
- package/src/panels/panel-manager.ts +570 -0
- package/src/panels/panel-picker.ts +106 -0
- package/src/panels/plan-dashboard-panel.ts +274 -0
- package/src/panels/plugins-panel.ts +178 -0
- package/src/panels/policy-panel.ts +308 -0
- package/src/panels/polish.ts +717 -0
- package/src/panels/project-planning-panel.ts +711 -0
- package/src/panels/provider-account-snapshot.ts +259 -0
- package/src/panels/provider-accounts-panel.ts +218 -0
- package/src/panels/provider-health-domains.ts +215 -0
- package/src/panels/provider-health-panel.ts +727 -0
- package/src/panels/provider-health-tracker.ts +115 -0
- package/src/panels/provider-stats-panel.ts +366 -0
- package/src/panels/qr-panel.ts +182 -0
- package/src/panels/remote-panel.ts +449 -0
- package/src/panels/routes-panel.ts +178 -0
- package/src/panels/sandbox-panel.ts +283 -0
- package/src/panels/schedule-panel.ts +329 -0
- package/src/panels/scrollable-list-panel.ts +491 -0
- package/src/panels/search-focus.ts +32 -0
- package/src/panels/security-panel.ts +295 -0
- package/src/panels/services-panel.ts +231 -0
- package/src/panels/session-browser-panel.ts +400 -0
- package/src/panels/session-maintenance.ts +125 -0
- package/src/panels/settings-sync-panel.ts +120 -0
- package/src/panels/skills-panel.ts +431 -0
- package/src/panels/subscription-panel.ts +263 -0
- package/src/panels/symbol-outline-panel.ts +486 -0
- package/src/panels/system-messages-panel.ts +230 -0
- package/src/panels/tasks-panel.ts +399 -0
- package/src/panels/thinking-panel.ts +304 -0
- package/src/panels/token-budget-panel.ts +475 -0
- package/src/panels/tool-inspector-panel.ts +429 -0
- package/src/panels/types.ts +54 -0
- package/src/panels/watchers-panel.ts +193 -0
- package/src/panels/work-plan-panel.ts +175 -0
- package/src/panels/worktree-panel.ts +182 -0
- package/src/panels/wrfc-panel.ts +609 -0
- package/src/permissions/prompt.ts +165 -0
- package/src/planning/project-planning-coordinator.ts +543 -0
- package/src/plugins/loader.ts +15 -0
- package/src/renderer/agent-detail-modal.ts +331 -0
- package/src/renderer/agent-workspace.ts +238 -0
- package/src/renderer/ansi-sanitize.ts +76 -0
- package/src/renderer/autocomplete-overlay.ts +154 -0
- package/src/renderer/block-actions.ts +76 -0
- package/src/renderer/bookmark-modal.ts +101 -0
- package/src/renderer/bottom-bar.ts +58 -0
- package/src/renderer/buffer.ts +113 -0
- package/src/renderer/code-block.ts +373 -0
- package/src/renderer/compositor.ts +283 -0
- package/src/renderer/context-inspector.ts +219 -0
- package/src/renderer/conversation-layout.ts +67 -0
- package/src/renderer/conversation-overlays.ts +140 -0
- package/src/renderer/conversation-surface.ts +260 -0
- package/src/renderer/diff-view.ts +132 -0
- package/src/renderer/diff.ts +130 -0
- package/src/renderer/file-picker-overlay.ts +101 -0
- package/src/renderer/file-tree.ts +153 -0
- package/src/renderer/fullscreen-primitives.ts +130 -0
- package/src/renderer/fullscreen-workspace.ts +199 -0
- package/src/renderer/git-status.ts +89 -0
- package/src/renderer/help-overlay.ts +267 -0
- package/src/renderer/history-search-overlay.ts +73 -0
- package/src/renderer/layout-engine.ts +97 -0
- package/src/renderer/layout.ts +32 -0
- package/src/renderer/live-tail-modal.ts +156 -0
- package/src/renderer/markdown.ts +635 -0
- package/src/renderer/mcp-workspace.ts +237 -0
- package/src/renderer/modal-factory.ts +467 -0
- package/src/renderer/modal-utils.ts +24 -0
- package/src/renderer/model-picker-overlay.ts +473 -0
- package/src/renderer/model-workspace.ts +488 -0
- package/src/renderer/onboarding/onboarding-wizard.ts +615 -0
- package/src/renderer/overlay-box.ts +146 -0
- package/src/renderer/overlay-viewport.ts +104 -0
- package/src/renderer/panel-composite.ts +158 -0
- package/src/renderer/panel-picker-overlay.ts +202 -0
- package/src/renderer/panel-tab-bar.ts +69 -0
- package/src/renderer/panel-workspace-bar.ts +42 -0
- package/src/renderer/process-indicator.ts +96 -0
- package/src/renderer/process-modal.ts +656 -0
- package/src/renderer/process-summary.ts +67 -0
- package/src/renderer/profile-picker-modal.ts +129 -0
- package/src/renderer/progress.ts +98 -0
- package/src/renderer/qr-renderer.ts +120 -0
- package/src/renderer/search-overlay.ts +54 -0
- package/src/renderer/selection-modal-overlay.ts +214 -0
- package/src/renderer/semantic-diff.ts +369 -0
- package/src/renderer/session-picker-modal.ts +127 -0
- package/src/renderer/settings-modal-helpers.ts +193 -0
- package/src/renderer/settings-modal.ts +537 -0
- package/src/renderer/shell-surface.ts +88 -0
- package/src/renderer/status-glyphs.ts +21 -0
- package/src/renderer/status-token.ts +67 -0
- package/src/renderer/surface-layout.ts +101 -0
- package/src/renderer/syntax-highlighter.ts +542 -0
- package/src/renderer/system-message.ts +83 -0
- package/src/renderer/tab-strip.ts +108 -0
- package/src/renderer/text-layout.ts +31 -0
- package/src/renderer/thinking.ts +17 -0
- package/src/renderer/tool-call.ts +234 -0
- package/src/renderer/ui-factory.ts +524 -0
- package/src/renderer/ui-primitives.ts +96 -0
- package/src/runtime/bootstrap-command-context.ts +278 -0
- package/src/runtime/bootstrap-command-parts.ts +386 -0
- package/src/runtime/bootstrap-core.ts +540 -0
- package/src/runtime/bootstrap-hook-bridge.ts +112 -0
- package/src/runtime/bootstrap-shell.ts +283 -0
- package/src/runtime/bootstrap.ts +575 -0
- package/src/runtime/cloudflare-control-plane.ts +349 -0
- package/src/runtime/context.ts +142 -0
- package/src/runtime/diagnostics/panels/index.ts +24 -0
- package/src/runtime/diagnostics/panels/ops.ts +156 -0
- package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
- package/src/runtime/diagnostics/panels/policy.ts +177 -0
- package/src/runtime/index.ts +662 -0
- package/src/runtime/onboarding/apply.ts +642 -0
- package/src/runtime/onboarding/derivation.ts +534 -0
- package/src/runtime/onboarding/index.ts +7 -0
- package/src/runtime/onboarding/markers.ts +148 -0
- package/src/runtime/onboarding/snapshot.ts +406 -0
- package/src/runtime/onboarding/state.ts +141 -0
- package/src/runtime/onboarding/types.ts +404 -0
- package/src/runtime/onboarding/verify.ts +171 -0
- package/src/runtime/operator-token-cleanup.ts +27 -0
- package/src/runtime/perf/panel-contracts.ts +32 -0
- package/src/runtime/perf/panel-health-monitor.ts +18 -0
- package/src/runtime/sandbox-public-gaps.ts +358 -0
- package/src/runtime/services.ts +670 -0
- package/src/runtime/store/domains/domain-read-matrix.ts +15 -0
- package/src/runtime/store/domains/index.ts +222 -0
- package/src/runtime/store/domains/panels.ts +117 -0
- package/src/runtime/store/domains/ui-perf.ts +103 -0
- package/src/runtime/store/index.ts +305 -0
- package/src/runtime/store/selectors/index.ts +359 -0
- package/src/runtime/store/state.ts +145 -0
- package/src/runtime/surface-feature-flags.ts +65 -0
- package/src/runtime/terminal-output-guard.ts +228 -0
- package/src/runtime/ui/index.ts +39 -0
- package/src/runtime/ui/model-picker/data-provider.ts +182 -0
- package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
- package/src/runtime/ui/model-picker/index.ts +59 -0
- package/src/runtime/ui/model-picker/types.ts +149 -0
- package/src/runtime/ui/provider-health/data-provider.ts +244 -0
- package/src/runtime/ui/provider-health/fallback-visualizer.ts +71 -0
- package/src/runtime/ui/provider-health/index.ts +46 -0
- package/src/runtime/ui/provider-health/types.ts +146 -0
- package/src/runtime/ui-events.ts +1 -0
- package/src/runtime/ui-read-model-helpers.ts +1 -0
- package/src/runtime/ui-read-models-observability-maintenance.ts +1 -0
- package/src/runtime/ui-read-models-observability-options.ts +1 -0
- package/src/runtime/ui-read-models-observability-remote.ts +1 -0
- package/src/runtime/ui-read-models-observability-security.ts +1 -0
- package/src/runtime/ui-read-models-observability-system.ts +1 -0
- package/src/runtime/ui-read-models-observability.ts +1 -0
- package/src/runtime/ui-read-models.ts +61 -0
- package/src/runtime/ui-service-queries.ts +1 -0
- package/src/runtime/ui-services.ts +190 -0
- package/src/scripts/process-messages.ts +42 -0
- package/src/shell/blocking-input.ts +98 -0
- package/src/shell/service-settings-sync.ts +273 -0
- package/src/shell/ui-openers.ts +352 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/wrfc-agent-guard.ts +49 -0
- package/src/types/grid.ts +48 -0
- package/src/types/sql-js.d.ts +15 -0
- package/src/utils/clipboard.ts +22 -0
- package/src/utils/splash-lines.ts +46 -0
- package/src/utils/terminal-width.ts +185 -0
- package/src/verification/live-verifier.ts +430 -0
- package/src/verification/verification-ledger.ts +242 -0
- package/src/version.ts +17 -0
- package/src/widget/index.ts +2 -0
- package/src/widget/types.ts +9 -0
- package/src/widget/widget.ts +8 -0
- package/src/work-plans/work-plan-store.ts +374 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* semantic-diff.ts — Functional change summary after file edits.
|
|
3
|
+
*
|
|
4
|
+
* Compares AST before/after an edit and extracts:
|
|
5
|
+
* - Added / removed / modified functions, methods, classes, etc.
|
|
6
|
+
* - Changed import specifiers
|
|
7
|
+
* - New / removed exports
|
|
8
|
+
*
|
|
9
|
+
* Returns a compact SemanticDiff that can be rendered alongside a regular diff.
|
|
10
|
+
* Gracefully returns null when tree-sitter is unavailable or the language is
|
|
11
|
+
* unsupported — callers should treat null as "no semantic info available".
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { TreeSitterService } from '@pellux/goodvibes-sdk/platform/intelligence';
|
|
15
|
+
import { extractSymbols } from '@pellux/goodvibes-sdk/platform/intelligence';
|
|
16
|
+
import type { SymbolInfo } from '@pellux/goodvibes-sdk/platform/intelligence';
|
|
17
|
+
import { detectLanguage } from '@pellux/goodvibes-sdk/platform/intelligence';
|
|
18
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
19
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Public types
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export type ChangeKind = 'added' | 'removed' | 'modified';
|
|
26
|
+
|
|
27
|
+
export interface SymbolChange {
|
|
28
|
+
kind: ChangeKind;
|
|
29
|
+
symbolKind: SymbolInfo['kind'];
|
|
30
|
+
name: string;
|
|
31
|
+
/** Present for 'modified' — the old signature. */
|
|
32
|
+
oldSignature?: string;
|
|
33
|
+
/** Present for 'added' or 'modified' — the new signature. */
|
|
34
|
+
newSignature?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ImportChange {
|
|
38
|
+
kind: ChangeKind;
|
|
39
|
+
specifier: string; // the module path, e.g. './utils'
|
|
40
|
+
/** Specific named imports that changed, if determinable. */
|
|
41
|
+
names?: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SemanticDiff {
|
|
45
|
+
symbols: SymbolChange[];
|
|
46
|
+
imports: ImportChange[];
|
|
47
|
+
/** Convenience: total number of changes across all categories. */
|
|
48
|
+
totalChanges: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Import extraction (regex-based — fast, no grammar needed)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
interface ParsedImport {
|
|
56
|
+
specifier: string;
|
|
57
|
+
names: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extract import specifiers and named imports from source text using a regex
|
|
62
|
+
* scan. Covers `import ... from '...'` and `import('...')` dynamic imports.
|
|
63
|
+
* Does not require tree-sitter.
|
|
64
|
+
*/
|
|
65
|
+
function extractImports(source: string): Map<string, ParsedImport> {
|
|
66
|
+
const map = new Map<string, ParsedImport>();
|
|
67
|
+
|
|
68
|
+
// Static imports: import { A, B } from './foo'
|
|
69
|
+
// import Foo from './foo'
|
|
70
|
+
// import * as Foo from './foo'
|
|
71
|
+
// import './side-effect'
|
|
72
|
+
const staticRe = /import\s+(?:([\s\S]*?)\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
73
|
+
let m: RegExpExecArray | null;
|
|
74
|
+
|
|
75
|
+
while ((m = staticRe.exec(source)) !== null) {
|
|
76
|
+
const clause = (m[1] ?? '').trim();
|
|
77
|
+
const specifier = m[2]!;
|
|
78
|
+
const names = parseNamedImports(clause);
|
|
79
|
+
const existing = map.get(specifier);
|
|
80
|
+
if (existing) {
|
|
81
|
+
for (const n of names) existing.names.push(n);
|
|
82
|
+
} else {
|
|
83
|
+
map.set(specifier, { specifier, names });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return map;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Parse `{ A, B as C }` or `* as Ns` or `Default` clauses into name strings. */
|
|
91
|
+
function parseNamedImports(clause: string): string[] {
|
|
92
|
+
if (!clause) return [];
|
|
93
|
+
// Namespace import: * as Foo
|
|
94
|
+
if (clause.startsWith('*')) return [clause];
|
|
95
|
+
// Named imports: { A, B as C }
|
|
96
|
+
const braceMatch = clause.match(/\{([^}]*)\}/);
|
|
97
|
+
if (braceMatch) {
|
|
98
|
+
return braceMatch[1]!
|
|
99
|
+
.split(',')
|
|
100
|
+
.map(s => s.trim())
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
}
|
|
103
|
+
// Default import: Foo
|
|
104
|
+
return clause ? [clause] : [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Symbol comparison
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
function diffSymbols(
|
|
112
|
+
before: SymbolInfo[],
|
|
113
|
+
after: SymbolInfo[],
|
|
114
|
+
): SymbolChange[] {
|
|
115
|
+
const changes: SymbolChange[] = [];
|
|
116
|
+
|
|
117
|
+
// Key by qualified name (container.name or name)
|
|
118
|
+
const key = (s: SymbolInfo): string =>
|
|
119
|
+
s.container ? `${s.container}.${s.name}` : s.name;
|
|
120
|
+
|
|
121
|
+
const beforeMap = new Map<string, SymbolInfo>();
|
|
122
|
+
const afterMap = new Map<string, SymbolInfo>();
|
|
123
|
+
|
|
124
|
+
for (const s of before) beforeMap.set(key(s), s);
|
|
125
|
+
for (const s of after) afterMap.set(key(s), s);
|
|
126
|
+
|
|
127
|
+
// Removed or modified
|
|
128
|
+
for (const [k, bSym] of beforeMap) {
|
|
129
|
+
const aSym = afterMap.get(k);
|
|
130
|
+
if (!aSym) {
|
|
131
|
+
changes.push({
|
|
132
|
+
kind: 'removed',
|
|
133
|
+
symbolKind: bSym.kind,
|
|
134
|
+
name: k,
|
|
135
|
+
oldSignature: bSym.signature,
|
|
136
|
+
});
|
|
137
|
+
} else if (
|
|
138
|
+
bSym.signature !== aSym.signature ||
|
|
139
|
+
bSym.exported !== aSym.exported
|
|
140
|
+
) {
|
|
141
|
+
changes.push({
|
|
142
|
+
kind: 'modified',
|
|
143
|
+
symbolKind: aSym.kind,
|
|
144
|
+
name: k,
|
|
145
|
+
oldSignature: bSym.signature,
|
|
146
|
+
newSignature: aSym.signature,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Added
|
|
152
|
+
for (const [k, aSym] of afterMap) {
|
|
153
|
+
if (!beforeMap.has(k)) {
|
|
154
|
+
changes.push({
|
|
155
|
+
kind: 'added',
|
|
156
|
+
symbolKind: aSym.kind,
|
|
157
|
+
name: k,
|
|
158
|
+
newSignature: aSym.signature,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return changes;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Import comparison
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
function diffImports(
|
|
171
|
+
before: Map<string, ParsedImport>,
|
|
172
|
+
after: Map<string, ParsedImport>,
|
|
173
|
+
): ImportChange[] {
|
|
174
|
+
const changes: ImportChange[] = [];
|
|
175
|
+
|
|
176
|
+
// Removed or modified
|
|
177
|
+
for (const [spec, bImp] of before) {
|
|
178
|
+
const aImp = after.get(spec);
|
|
179
|
+
if (!aImp) {
|
|
180
|
+
changes.push({ kind: 'removed', specifier: spec, names: bImp.names });
|
|
181
|
+
} else {
|
|
182
|
+
// Check if named imports changed
|
|
183
|
+
const bSet = new Set(bImp.names);
|
|
184
|
+
const aSet = new Set(aImp.names);
|
|
185
|
+
const added = aImp.names.filter(n => !bSet.has(n));
|
|
186
|
+
const removed = bImp.names.filter(n => !aSet.has(n));
|
|
187
|
+
if (added.length > 0 || removed.length > 0) {
|
|
188
|
+
const names: string[] = [
|
|
189
|
+
...added.map(n => `+${n}`),
|
|
190
|
+
...removed.map(n => `-${n}`),
|
|
191
|
+
];
|
|
192
|
+
changes.push({ kind: 'modified', specifier: spec, names });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Added
|
|
198
|
+
for (const [spec, aImp] of after) {
|
|
199
|
+
if (!before.has(spec)) {
|
|
200
|
+
changes.push({ kind: 'added', specifier: spec, names: aImp.names });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return changes;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Public API
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Compare `beforeContent` and `afterContent` for the given `filePath` and
|
|
213
|
+
* return a SemanticDiff describing functional changes.
|
|
214
|
+
*
|
|
215
|
+
* Returns null when:
|
|
216
|
+
* - The language is not supported by tree-sitter
|
|
217
|
+
* - Tree-sitter is not initialized
|
|
218
|
+
* - Parsing fails
|
|
219
|
+
*
|
|
220
|
+
* Import changes are always returned (regex-based, no grammar required).
|
|
221
|
+
*/
|
|
222
|
+
export async function computeSemanticDiff(
|
|
223
|
+
filePath: string,
|
|
224
|
+
beforeContent: string,
|
|
225
|
+
afterContent: string,
|
|
226
|
+
): Promise<SemanticDiff | null> {
|
|
227
|
+
// Import diff is always available (regex-based)
|
|
228
|
+
const beforeImports = extractImports(beforeContent);
|
|
229
|
+
const afterImports = extractImports(afterContent);
|
|
230
|
+
const imports = diffImports(beforeImports, afterImports);
|
|
231
|
+
|
|
232
|
+
const langId = detectLanguage(filePath);
|
|
233
|
+
if (!langId) {
|
|
234
|
+
// No language support — return import changes only if any
|
|
235
|
+
if (imports.length === 0) return null;
|
|
236
|
+
return { symbols: [], imports, totalChanges: imports.length };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const svc = new TreeSitterService();
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
await svc.initialize();
|
|
243
|
+
} catch {
|
|
244
|
+
logger.debug('computeSemanticDiff: tree-sitter init failed', { filePath });
|
|
245
|
+
if (imports.length === 0) return null;
|
|
246
|
+
return { symbols: [], imports, totalChanges: imports.length };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Use a temporary key so we don't pollute the service cache with diff content.
|
|
250
|
+
// Include a nonce so concurrent calls for the same filePath don't collide.
|
|
251
|
+
const nonce = Math.random().toString(36).slice(2);
|
|
252
|
+
const beforeKey = `__semantic_diff_before__${nonce}_${filePath}`;
|
|
253
|
+
const afterKey = `__semantic_diff_after__${nonce}_${filePath}`;
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const [beforeTree, afterTree] = await Promise.all([
|
|
257
|
+
svc.parse(beforeKey, beforeContent, langId),
|
|
258
|
+
svc.parse(afterKey, afterContent, langId),
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
if (!beforeTree || !afterTree) {
|
|
262
|
+
if (imports.length === 0) return null;
|
|
263
|
+
return { symbols: [], imports, totalChanges: imports.length };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// loadLanguage returns from cache since parse() already loaded it
|
|
267
|
+
const lang = await svc.loadLanguage(langId);
|
|
268
|
+
if (!lang) {
|
|
269
|
+
if (imports.length === 0) return null;
|
|
270
|
+
return { symbols: [], imports, totalChanges: imports.length };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const beforeSymbols = extractSymbols(beforeTree, lang, langId);
|
|
274
|
+
const afterSymbols = extractSymbols(afterTree, lang, langId);
|
|
275
|
+
const symbols = diffSymbols(beforeSymbols, afterSymbols);
|
|
276
|
+
|
|
277
|
+
// Clean up temporary cache entries
|
|
278
|
+
svc.invalidate(beforeKey);
|
|
279
|
+
svc.invalidate(afterKey);
|
|
280
|
+
|
|
281
|
+
const totalChanges = symbols.length + imports.length;
|
|
282
|
+
if (totalChanges === 0) return null;
|
|
283
|
+
|
|
284
|
+
return { symbols, imports, totalChanges };
|
|
285
|
+
} catch (err) {
|
|
286
|
+
logger.error('computeSemanticDiff: comparison failed', {
|
|
287
|
+
filePath,
|
|
288
|
+
error: summarizeError(err),
|
|
289
|
+
});
|
|
290
|
+
svc.invalidate(beforeKey);
|
|
291
|
+
svc.invalidate(afterKey);
|
|
292
|
+
if (imports.length === 0) return null;
|
|
293
|
+
return { symbols: [], imports, totalChanges: imports.length };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// Display formatting
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
const KIND_ICON: Record<SymbolInfo['kind'], string> = {
|
|
302
|
+
function: 'fn',
|
|
303
|
+
method: 'method',
|
|
304
|
+
class: 'class',
|
|
305
|
+
interface: 'iface',
|
|
306
|
+
type: 'type',
|
|
307
|
+
variable: 'var',
|
|
308
|
+
constant: 'const',
|
|
309
|
+
enum: 'enum',
|
|
310
|
+
property: 'prop',
|
|
311
|
+
namespace: 'ns',
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const CHANGE_GLYPH: Record<ChangeKind, string> = {
|
|
315
|
+
added: '+',
|
|
316
|
+
removed: '-',
|
|
317
|
+
modified: '~',
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Render a SemanticDiff as an array of compact summary strings, one change
|
|
322
|
+
* per line. Suitable for display in the diff panel status area or a tooltip.
|
|
323
|
+
*
|
|
324
|
+
* Format examples:
|
|
325
|
+
* + fn renderDiffView
|
|
326
|
+
* ~ method DiffPanel.showDiff (signature changed)
|
|
327
|
+
* - class OldWidget
|
|
328
|
+
* + import ./utils { A, B }
|
|
329
|
+
* ~ import ./types (+NewType, -OldType)
|
|
330
|
+
*/
|
|
331
|
+
export function formatSemanticDiff(diff: SemanticDiff): string[] {
|
|
332
|
+
const lines: string[] = [];
|
|
333
|
+
|
|
334
|
+
// Symbol changes first
|
|
335
|
+
for (const sc of diff.symbols) {
|
|
336
|
+
const glyph = CHANGE_GLYPH[sc.kind];
|
|
337
|
+
const icon = KIND_ICON[sc.symbolKind];
|
|
338
|
+
let line = `${glyph} ${icon} ${sc.name}`;
|
|
339
|
+
if (sc.kind === 'modified') {
|
|
340
|
+
line += ' (signature changed)';
|
|
341
|
+
}
|
|
342
|
+
lines.push(line);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Import changes
|
|
346
|
+
for (const ic of diff.imports) {
|
|
347
|
+
const glyph = CHANGE_GLYPH[ic.kind];
|
|
348
|
+
let line = `${glyph} import ${ic.specifier}`;
|
|
349
|
+
if (ic.names && ic.names.length > 0) {
|
|
350
|
+
const nameList = ic.names.slice(0, 4).join(', ');
|
|
351
|
+
const overflow = ic.names.length > 4 ? ` +${ic.names.length - 4} more` : '';
|
|
352
|
+
line += ` { ${nameList}${overflow} }`;
|
|
353
|
+
}
|
|
354
|
+
lines.push(line);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return lines;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Render a one-line summary suitable for a status bar.
|
|
362
|
+
* Example: "3 changes: +fn renderX ~method Foo.bar -import ./old"
|
|
363
|
+
*/
|
|
364
|
+
export function formatSemanticDiffSummary(diff: SemanticDiff): string {
|
|
365
|
+
if (diff.totalChanges === 0) return '';
|
|
366
|
+
const parts = formatSemanticDiff(diff).slice(0, 3);
|
|
367
|
+
const overflow = diff.totalChanges > 3 ? ` +${diff.totalChanges - 3} more` : '';
|
|
368
|
+
return `${diff.totalChanges} semantic change${diff.totalChanges !== 1 ? 's' : ''}: ${parts.join(' ')}${overflow}`;
|
|
369
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderSessionPickerModal — renders the /sessions picker modal as Line[]
|
|
3
|
+
* using ModalFactory.
|
|
4
|
+
*
|
|
5
|
+
* Shows a list of saved sessions with:
|
|
6
|
+
* - name, timestamp (formatted), message count
|
|
7
|
+
* Footer hints: [Enter] Load [d] Delete [Esc] Close
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Line } from '../types/grid.ts';
|
|
11
|
+
import { ModalFactory } from './modal-factory.ts';
|
|
12
|
+
import type { SessionPickerModal } from '../input/session-picker-modal.ts';
|
|
13
|
+
import { formatTimestamp } from './modal-utils.ts';
|
|
14
|
+
import { fitDisplay } from '../utils/terminal-width.ts';
|
|
15
|
+
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Renderer
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render the session picker modal as Line[] for overlay in the viewport.
|
|
23
|
+
*
|
|
24
|
+
* @param modal SessionPickerModal state object.
|
|
25
|
+
* @param width Terminal width.
|
|
26
|
+
*/
|
|
27
|
+
export function renderSessionPickerModal(
|
|
28
|
+
modal: SessionPickerModal,
|
|
29
|
+
width: number,
|
|
30
|
+
viewportHeight = 24,
|
|
31
|
+
): Line[] {
|
|
32
|
+
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
33
|
+
chromeRows: 6,
|
|
34
|
+
minContentRows: 5,
|
|
35
|
+
maxContentRows: 9,
|
|
36
|
+
});
|
|
37
|
+
const boxMargin = metrics.margin;
|
|
38
|
+
const boxW = metrics.boxWidth;
|
|
39
|
+
const contentW = metrics.contentWidth;
|
|
40
|
+
const visibleRows = metrics.contentRows;
|
|
41
|
+
const targetContentRows = getStableOverlayContentRows(metrics.contentRows, 8);
|
|
42
|
+
modal.setVisibleRows(visibleRows);
|
|
43
|
+
|
|
44
|
+
const sections: import('./modal-factory.ts').ModalSection[] = [];
|
|
45
|
+
|
|
46
|
+
if (modal.sessions.length === 0) {
|
|
47
|
+
sections.push({
|
|
48
|
+
type: 'text',
|
|
49
|
+
content: 'No saved sessions.',
|
|
50
|
+
style: { fg: '244', dim: true },
|
|
51
|
+
});
|
|
52
|
+
sections.push({
|
|
53
|
+
type: 'text',
|
|
54
|
+
content: 'Use /save [name] to save the current session.',
|
|
55
|
+
style: { fg: '240', dim: true },
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
// Column widths: name(24) | timestamp(16) | messages(remaining)
|
|
59
|
+
const nameW = 24;
|
|
60
|
+
const tsW = 16;
|
|
61
|
+
const msgW = Math.max(4, contentW - nameW - tsW - 4); // 4 = separators/spaces
|
|
62
|
+
|
|
63
|
+
// Column header
|
|
64
|
+
const nameHdr = fitDisplay('Name', nameW);
|
|
65
|
+
const tsHdr = fitDisplay('Saved', tsW);
|
|
66
|
+
const msgHdr = fitDisplay('Msgs', msgW);
|
|
67
|
+
sections.push({
|
|
68
|
+
type: 'text',
|
|
69
|
+
content: `${nameHdr} ${tsHdr} ${msgHdr}`,
|
|
70
|
+
style: { fg: '240', dim: true },
|
|
71
|
+
});
|
|
72
|
+
sections.push({ type: 'separator' });
|
|
73
|
+
|
|
74
|
+
const visibleSessions = modal.sessions.slice(modal.scrollOffset, modal.scrollOffset + visibleRows);
|
|
75
|
+
const listItems: import('./modal-factory.ts').ModalListItem[] = visibleSessions.map((sess, idx) => {
|
|
76
|
+
const isSelected = modal.scrollOffset + idx === modal.selectedIndex;
|
|
77
|
+
|
|
78
|
+
const nameStr = fitDisplay(sess.name, nameW);
|
|
79
|
+
|
|
80
|
+
const tsStr = fitDisplay(formatTimestamp(sess.timestamp), tsW);
|
|
81
|
+
const msgStr = fitDisplay(String(sess.messageCount), msgW);
|
|
82
|
+
|
|
83
|
+
const label = `${nameStr} ${tsStr} ${msgStr}`;
|
|
84
|
+
return { label, selected: isSelected };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
sections.push({ type: 'list', items: listItems });
|
|
88
|
+
if (modal.sessions.length > visibleRows) {
|
|
89
|
+
sections.push({ type: 'separator' });
|
|
90
|
+
sections.push({
|
|
91
|
+
type: 'text',
|
|
92
|
+
content: `[${modal.scrollOffset + 1}-${Math.min(modal.sessions.length, modal.scrollOffset + visibleRows)} of ${modal.sessions.length}]`,
|
|
93
|
+
style: { fg: '244', dim: true },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Status message if present
|
|
99
|
+
if (modal.statusMessage) {
|
|
100
|
+
sections.push({ type: 'separator' });
|
|
101
|
+
sections.push({
|
|
102
|
+
type: 'text',
|
|
103
|
+
content: modal.statusMessage,
|
|
104
|
+
style: { fg: modal.deleteConfirmationTarget ? '#f59e0b' : '#00ffcc' },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (modal.deleteConfirmationTarget) {
|
|
109
|
+
sections.push({
|
|
110
|
+
type: 'text',
|
|
111
|
+
content: `Deletion is armed for ${modal.deleteConfirmationTarget}. Move selection or press Esc to cancel.`,
|
|
112
|
+
style: { fg: '244', dim: true },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return ModalFactory.createModal(
|
|
117
|
+
{
|
|
118
|
+
title: 'Sessions',
|
|
119
|
+
width: boxW,
|
|
120
|
+
margin: boxMargin,
|
|
121
|
+
targetContentRows,
|
|
122
|
+
sections,
|
|
123
|
+
hints: ['[\u2191\u2193] Navigate', '[Enter] Load', '[d] Arm / Delete', '[Esc] Close'],
|
|
124
|
+
},
|
|
125
|
+
width,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure formatting, label, and color helpers for renderSettingsModal.
|
|
3
|
+
* Extracted from settings-modal.ts to keep the renderer under the 800-line
|
|
4
|
+
* architecture cap. No layout logic lives here.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SettingEntry, McpEntry, SubscriptionEntry } from '../input/settings-modal.ts';
|
|
8
|
+
import { SETTINGS_CATEGORIES } from '../input/settings-modal.ts';
|
|
9
|
+
import { isSecretConfigKey, isSecretReferenceValue } from '../config/secret-config.ts';
|
|
10
|
+
|
|
11
|
+
function maskSecretValue(value: string): string {
|
|
12
|
+
if (value.length === 0) return '(empty)';
|
|
13
|
+
if (isSecretReferenceValue(value)) return value;
|
|
14
|
+
if (value.length <= 4) return '••••';
|
|
15
|
+
return `${'•'.repeat(Math.min(12, Math.max(4, value.length - 4)))}${value.slice(-4)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatValue(entry: SettingEntry): string {
|
|
19
|
+
const val = entry.currentValue;
|
|
20
|
+
if (val === null || val === undefined) return '(unset)';
|
|
21
|
+
if (typeof val === 'boolean') return val ? 'true' : 'false';
|
|
22
|
+
if (typeof val === 'string' && isSecretConfigKey(entry.setting.key)) return maskSecretValue(val);
|
|
23
|
+
if (typeof val === 'string' && val === '') return '(empty)';
|
|
24
|
+
return String(val);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function valueColor(entry: SettingEntry): string {
|
|
28
|
+
if (!entry.isDefault) return '#00ffcc'; // cyan-green = modified
|
|
29
|
+
return '244'; // dim = default
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function flagStateColor(state: string, killed: boolean): string {
|
|
33
|
+
if (killed) return '#ef4444'; // red
|
|
34
|
+
if (state === 'enabled') return '#00ffcc'; // cyan-green
|
|
35
|
+
return '244'; // dim
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function mcpTrustColor(mode: McpEntry['trustMode']): string {
|
|
39
|
+
switch (mode) {
|
|
40
|
+
case 'allow-all':
|
|
41
|
+
return '#ef4444';
|
|
42
|
+
case 'ask-on-risk':
|
|
43
|
+
return '#eab308';
|
|
44
|
+
case 'constrained':
|
|
45
|
+
return '#00ffcc';
|
|
46
|
+
case 'blocked':
|
|
47
|
+
return '244';
|
|
48
|
+
default:
|
|
49
|
+
return '244';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function subscriptionStateColor(state: SubscriptionEntry['state']): string {
|
|
54
|
+
switch (state) {
|
|
55
|
+
case 'active':
|
|
56
|
+
return '#00ffcc';
|
|
57
|
+
case 'pending':
|
|
58
|
+
return '#eab308';
|
|
59
|
+
case 'available':
|
|
60
|
+
return '#38bdf8';
|
|
61
|
+
default:
|
|
62
|
+
return '244';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function inferSubscriptionRouteReason(entry: SubscriptionEntry): string | undefined {
|
|
67
|
+
if (entry.routeReason?.trim()) return entry.routeReason;
|
|
68
|
+
if (entry.state === 'active' && entry.oauthConfigured) {
|
|
69
|
+
return 'ambient key override enabled for this provider.';
|
|
70
|
+
}
|
|
71
|
+
if (entry.state === 'pending' && entry.oauthConfigured) {
|
|
72
|
+
return 'oauth configuration present; ambient key override will apply after activation.';
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const CATEGORY_LABELS: Record<(typeof SETTINGS_CATEGORIES)[number], string> = {
|
|
78
|
+
display: 'Display',
|
|
79
|
+
ui: 'UI',
|
|
80
|
+
provider: 'Provider',
|
|
81
|
+
subscriptions: 'Subscriptions',
|
|
82
|
+
behavior: 'Behavior',
|
|
83
|
+
storage: 'Storage',
|
|
84
|
+
permissions: 'Permissions',
|
|
85
|
+
orchestration: 'Orchestration',
|
|
86
|
+
wrfc: 'WRFC Delegation',
|
|
87
|
+
helper: 'Helper',
|
|
88
|
+
tts: 'TTS',
|
|
89
|
+
service: 'Service',
|
|
90
|
+
controlPlane: 'Control Plane',
|
|
91
|
+
httpListener: 'HTTP Listener',
|
|
92
|
+
web: 'Web',
|
|
93
|
+
batch: 'Batch',
|
|
94
|
+
automation: 'Automation',
|
|
95
|
+
watchers: 'Watchers',
|
|
96
|
+
runtime: 'Runtime',
|
|
97
|
+
telemetry: 'Telemetry',
|
|
98
|
+
cache: 'Cache',
|
|
99
|
+
mcp: 'MCP',
|
|
100
|
+
sandbox: 'External Sandbox',
|
|
101
|
+
surfaces: 'Surfaces',
|
|
102
|
+
cloudflare: 'Cloudflare',
|
|
103
|
+
release: 'Release',
|
|
104
|
+
danger: 'Danger',
|
|
105
|
+
tools: 'Tools',
|
|
106
|
+
flags: 'Feature Flags',
|
|
107
|
+
network: 'Network',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const SETTING_LABELS: Partial<Record<string, string>> = {
|
|
111
|
+
'ui.systemMessages': 'System Message Target',
|
|
112
|
+
'ui.operationalMessages': 'Operational Message Target',
|
|
113
|
+
'ui.wrfcMessages': 'Delegated WRFC Message Target',
|
|
114
|
+
'ui.voiceEnabled': 'Voice Surface',
|
|
115
|
+
'behavior.autoCompactThreshold': 'Auto-Compact %',
|
|
116
|
+
'behavior.staleContextWarnings': 'Context Warnings',
|
|
117
|
+
'behavior.returnContextMode': 'Return Context',
|
|
118
|
+
'behavior.guidanceMode': 'Guidance Mode',
|
|
119
|
+
'storage.secretPolicy': 'Secret Policy',
|
|
120
|
+
'sandbox.vmBackend': 'External Sandbox Backend',
|
|
121
|
+
'sandbox.qemuBinary': 'External QEMU Binary',
|
|
122
|
+
'sandbox.qemuImagePath': 'External QEMU Image',
|
|
123
|
+
'sandbox.qemuExecWrapper': 'External QEMU Wrapper',
|
|
124
|
+
'sandbox.replJavaScriptCommand': 'REPL JS Command',
|
|
125
|
+
'tools.llmProvider': 'Tool LLM Provider',
|
|
126
|
+
'tools.llmModel': 'Tool LLM Model',
|
|
127
|
+
'tools.autoHeal': 'Auto-Heal',
|
|
128
|
+
'tools.defaultTokenBudget': 'Default Token Budget',
|
|
129
|
+
'tools.hooksFile': 'Hooks File',
|
|
130
|
+
'helper.enabled': 'Helper Enabled',
|
|
131
|
+
'helper.globalProvider': 'Helper Provider',
|
|
132
|
+
'helper.globalModel': 'Helper Model',
|
|
133
|
+
// Control Plane
|
|
134
|
+
'controlPlane.enabled': 'CP Enabled',
|
|
135
|
+
'controlPlane.hostMode': 'CP Host Mode',
|
|
136
|
+
'controlPlane.host': 'CP Host',
|
|
137
|
+
'controlPlane.port': 'CP Port',
|
|
138
|
+
'controlPlane.baseUrl': 'CP Base URL',
|
|
139
|
+
'controlPlane.streamMode': 'CP Stream Mode',
|
|
140
|
+
'controlPlane.allowRemote': 'CP Allow Remote',
|
|
141
|
+
'controlPlane.trustProxy': 'CP Trust Proxy',
|
|
142
|
+
'controlPlane.tls.mode': 'CP TLS Mode',
|
|
143
|
+
'controlPlane.tls.certFile': 'CP TLS Cert',
|
|
144
|
+
'controlPlane.tls.keyFile': 'CP TLS Key',
|
|
145
|
+
// HTTP Listener
|
|
146
|
+
'httpListener.hostMode': 'HTTP Host Mode',
|
|
147
|
+
'httpListener.host': 'HTTP Host',
|
|
148
|
+
'httpListener.port': 'HTTP Port',
|
|
149
|
+
'httpListener.trustProxy': 'HTTP Trust Proxy',
|
|
150
|
+
'httpListener.tls.mode': 'HTTP TLS Mode',
|
|
151
|
+
'httpListener.tls.certFile': 'HTTP TLS Cert',
|
|
152
|
+
// Web Server
|
|
153
|
+
'web.enabled': 'Web Enabled',
|
|
154
|
+
'web.hostMode': 'Web Host Mode',
|
|
155
|
+
'web.host': 'Web Host',
|
|
156
|
+
'web.port': 'Web Port',
|
|
157
|
+
'web.publicBaseUrl': 'Web Public Base URL',
|
|
158
|
+
'web.staticAssetsDir': 'Web Static Assets Dir',
|
|
159
|
+
'surfaces.ntfy.enabled': 'ntfy Enabled',
|
|
160
|
+
'surfaces.ntfy.baseUrl': 'ntfy Base URL',
|
|
161
|
+
'surfaces.ntfy.topic': 'ntfy Default Delivery Topic',
|
|
162
|
+
'surfaces.ntfy.chatTopic': 'ntfy Chat Topic',
|
|
163
|
+
'surfaces.ntfy.agentTopic': 'ntfy Agent Topic',
|
|
164
|
+
'surfaces.ntfy.remoteTopic': 'ntfy Daemon-Only Remote Topic',
|
|
165
|
+
'surfaces.ntfy.token': 'ntfy Token',
|
|
166
|
+
'surfaces.ntfy.defaultPriority': 'ntfy Default Priority',
|
|
167
|
+
'surfaces.homeassistant.enabled': 'Home Assistant Enabled',
|
|
168
|
+
'surfaces.homeassistant.instanceUrl': 'Home Assistant URL',
|
|
169
|
+
'surfaces.homeassistant.accessToken': 'Home Assistant Access Token',
|
|
170
|
+
'surfaces.homeassistant.webhookSecret': 'Home Assistant Webhook Secret',
|
|
171
|
+
'surfaces.homeassistant.defaultConversationId': 'Home Assistant Conversation ID',
|
|
172
|
+
'surfaces.homeassistant.remoteSessionTtlMs': 'Home Assistant Remote Session TTL',
|
|
173
|
+
'surfaces.homeassistant.deviceId': 'Home Assistant Device ID',
|
|
174
|
+
'surfaces.homeassistant.deviceName': 'Home Assistant Device Name',
|
|
175
|
+
'surfaces.homeassistant.eventType': 'Home Assistant Event Type',
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export function getSettingLabel(entry: SettingEntry): string {
|
|
179
|
+
return SETTING_LABELS[entry.setting.key] ?? entry.setting.key.replace(/^[^.]+\./, '');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function describeUiRouting(value: string): string {
|
|
183
|
+
switch (value) {
|
|
184
|
+
case 'panel':
|
|
185
|
+
return 'render in panels only';
|
|
186
|
+
case 'conversation':
|
|
187
|
+
return 'render inline in conversation';
|
|
188
|
+
case 'both':
|
|
189
|
+
return 'render in both conversation and panels';
|
|
190
|
+
default:
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
}
|