@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,434 @@
|
|
|
1
|
+
import { ServiceRegistry } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
|
+
import { evaluateSessionMaintenance, formatSessionMaintenanceLines } from '@/runtime/index.ts';
|
|
4
|
+
import { estimateConversationTokens } from '@pellux/goodvibes-sdk/platform/core';
|
|
5
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
6
|
+
import { buildSetupReviewSnapshot } from './local-setup-review.ts';
|
|
7
|
+
import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
|
|
8
|
+
import { getSettingsControlPlaneSnapshot } from '@/runtime/index.ts';
|
|
9
|
+
import { listPersistedWorktreeMeta, summarizeWorktreeOwnership } from '@/runtime/index.ts';
|
|
10
|
+
import { checkRecoveryFile, readLastSessionPointer } from '@/runtime/index.ts';
|
|
11
|
+
import {
|
|
12
|
+
openCommandPanel,
|
|
13
|
+
requireLocalUserAuthManager,
|
|
14
|
+
requireOperatorClient,
|
|
15
|
+
requireProviderApi,
|
|
16
|
+
requireReadModels,
|
|
17
|
+
requireSecretsManager,
|
|
18
|
+
requireServiceRegistry,
|
|
19
|
+
requireSubscriptionManager,
|
|
20
|
+
requireSessionMemoryStore,
|
|
21
|
+
} from './runtime-services.ts';
|
|
22
|
+
|
|
23
|
+
function renderSandboxHealthSummary(configManager: ConfigManager): string[] {
|
|
24
|
+
const backend = String(configManager.get('sandbox.vmBackend') ?? 'local');
|
|
25
|
+
const imagePath = String(configManager.get('sandbox.qemuImagePath') ?? '').trim();
|
|
26
|
+
const wrapperPath = String(configManager.get('sandbox.qemuExecWrapper') ?? '').trim();
|
|
27
|
+
const lines = [
|
|
28
|
+
` backend: ${backend}`,
|
|
29
|
+
` qemu image: ${imagePath || '(not configured)'}`,
|
|
30
|
+
` qemu wrapper: ${wrapperPath || '(not configured)'}`,
|
|
31
|
+
];
|
|
32
|
+
if (backend === 'qemu' && !imagePath) lines.push(' issue: qemu backend selected without qemuImagePath');
|
|
33
|
+
if (backend === 'qemu' && !wrapperPath) lines.push(' issue: qemu backend selected without qemuExecWrapper');
|
|
34
|
+
return lines;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
38
|
+
registry.register({
|
|
39
|
+
name: 'health',
|
|
40
|
+
aliases: ['doctor'],
|
|
41
|
+
description: 'Health workspace for startup posture, service readiness, sandbox posture, and provider health',
|
|
42
|
+
usage: '[open|review|setup|services|sandbox|provider|accounts|auth|settings|intelligence|remote|mcp|continuity|worktrees|maintenance|repair [domain]]',
|
|
43
|
+
async handler(args, ctx) {
|
|
44
|
+
const sub = (args[0] ?? 'review').toLowerCase();
|
|
45
|
+
const readModels = requireReadModels(ctx);
|
|
46
|
+
|
|
47
|
+
if (sub === 'open' || sub === 'panel' || sub === 'provider') {
|
|
48
|
+
openCommandPanel(ctx, 'provider-health');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (sub === 'services') {
|
|
53
|
+
const registry = requireServiceRegistry(ctx);
|
|
54
|
+
const all = registry.getAll();
|
|
55
|
+
const keys = Object.keys(all);
|
|
56
|
+
const inspections = await Promise.all(keys.map((name) => registry.inspect(name)));
|
|
57
|
+
const issues = inspections
|
|
58
|
+
.filter((inspection): inspection is NonNullable<typeof inspection> => inspection !== null)
|
|
59
|
+
.flatMap((inspection) => {
|
|
60
|
+
const findings: string[] = [];
|
|
61
|
+
if (!inspection.hasPrimaryCredential) findings.push(`${inspection.config.name}: missing primary credential`);
|
|
62
|
+
if (inspection.config.authType === 'basic' && !inspection.hasPasswordCredential) findings.push(`${inspection.config.name}: missing password credential`);
|
|
63
|
+
if (!inspection.config.baseUrl) findings.push(`${inspection.config.name}: no baseUrl configured`);
|
|
64
|
+
return findings;
|
|
65
|
+
});
|
|
66
|
+
ctx.print([
|
|
67
|
+
'Health Review: Services',
|
|
68
|
+
` configured: ${keys.length}`,
|
|
69
|
+
` issues: ${issues.length}`,
|
|
70
|
+
...(issues.length > 0 ? issues.map((issue) => ` ${issue}`) : [' all configured services passed readiness checks']),
|
|
71
|
+
].join('\n'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (sub === 'sandbox') {
|
|
76
|
+
ctx.print([
|
|
77
|
+
'Health Review: Sandbox',
|
|
78
|
+
...renderSandboxHealthSummary(ctx.platform.configManager),
|
|
79
|
+
].join('\n'));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (sub === 'accounts') {
|
|
84
|
+
const operatorClient = requireOperatorClient(ctx);
|
|
85
|
+
const accounts = await operatorClient.providers.accountSnapshot();
|
|
86
|
+
ctx.print([
|
|
87
|
+
'Health Review: Accounts',
|
|
88
|
+
` providers: ${accounts.providers.length}`,
|
|
89
|
+
` configured: ${accounts.configuredCount}`,
|
|
90
|
+
` issues: ${accounts.issueCount}`,
|
|
91
|
+
...accounts.providers.flatMap((provider) => {
|
|
92
|
+
const findings = [
|
|
93
|
+
...provider.issues.map((issue) => ` ${provider.providerId}: ${issue}`),
|
|
94
|
+
...(provider.fallbackRisk ? [` ${provider.providerId}: ${provider.fallbackRisk}`] : []),
|
|
95
|
+
];
|
|
96
|
+
return findings;
|
|
97
|
+
}),
|
|
98
|
+
].join('\n'));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (sub === 'auth') {
|
|
103
|
+
const auth = readModels.localAuth.getSnapshot();
|
|
104
|
+
ctx.print([
|
|
105
|
+
'Health Review: Local Auth',
|
|
106
|
+
` users: ${auth.userCount}`,
|
|
107
|
+
` sessions: ${auth.sessionCount}`,
|
|
108
|
+
` bootstrap file: ${auth.bootstrapCredentialPresent ? 'present' : 'cleared'}`,
|
|
109
|
+
...(auth.userCount <= 1 ? [' issue: only one local auth user configured'] : []),
|
|
110
|
+
...(auth.bootstrapCredentialPresent ? [' issue: bootstrap credential file still present; rotate or clear it when no longer needed'] : []),
|
|
111
|
+
].join('\n'));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (sub === 'settings') {
|
|
116
|
+
const settings = readModels.settings.getSnapshot();
|
|
117
|
+
const issues: string[] = [];
|
|
118
|
+
if (settings.conflictCount > 0) issues.push(`${settings.conflictCount} conflicting setting import(s) need review`);
|
|
119
|
+
if (settings.recentFailureCount > 0) issues.push(`${settings.recentFailureCount} recent sync/managed failure(s) recorded`);
|
|
120
|
+
if (settings.hasStagedManagedBundle) issues.push('staged managed bundle is awaiting apply or rollback');
|
|
121
|
+
if (settings.managedLockCount > 0) issues.push(`${settings.managedLockCount} managed lock(s) currently enforced`);
|
|
122
|
+
ctx.print([
|
|
123
|
+
'Health Review: Settings',
|
|
124
|
+
` available: ${settings.available ? 'yes' : 'no'}`,
|
|
125
|
+
` managed locks: ${settings.managedLockCount}`,
|
|
126
|
+
` conflicts: ${settings.conflictCount}`,
|
|
127
|
+
` recent failures: ${settings.recentFailureCount}`,
|
|
128
|
+
` staged bundle: ${settings.hasStagedManagedBundle ? 'present' : 'none'}`,
|
|
129
|
+
...(issues.length > 0 ? issues.map((issue) => ` issue: ${issue}`) : [' no active settings-control issues detected']),
|
|
130
|
+
' next: /settingssync panel',
|
|
131
|
+
' next: /settingssync show <key>',
|
|
132
|
+
' next: /managed staged',
|
|
133
|
+
].join('\n'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (sub === 'intelligence') {
|
|
138
|
+
const intelligence = readModels.intelligence.getSnapshot();
|
|
139
|
+
const issues: string[] = [];
|
|
140
|
+
if (intelligence.diagnosticsStatus !== 'ready') issues.push(`diagnostics=${intelligence.diagnosticsStatus}`);
|
|
141
|
+
if (intelligence.symbolSearchStatus !== 'ready') issues.push(`symbols=${intelligence.symbolSearchStatus}`);
|
|
142
|
+
if (intelligence.completionsStatus !== 'ready') issues.push(`completions=${intelligence.completionsStatus}`);
|
|
143
|
+
if (intelligence.hoverStatus !== 'ready') issues.push(`hover=${intelligence.hoverStatus}`);
|
|
144
|
+
ctx.print([
|
|
145
|
+
'Health Review: Intelligence',
|
|
146
|
+
` diagnostics: ${intelligence.diagnosticsStatus}`,
|
|
147
|
+
` symbols: ${intelligence.symbolSearchStatus}`,
|
|
148
|
+
` completions: ${intelligence.completionsStatus}`,
|
|
149
|
+
` hover: ${intelligence.hoverStatus}`,
|
|
150
|
+
` errors: ${intelligence.errorCount}`,
|
|
151
|
+
` warnings: ${intelligence.warningCount}`,
|
|
152
|
+
` requests: ${intelligence.totalRequests}`,
|
|
153
|
+
` avg latency: ${Math.round(intelligence.avgLatencyMs)}ms`,
|
|
154
|
+
...(issues.length > 0
|
|
155
|
+
? issues.map((issue) => ` issue: ${issue}`)
|
|
156
|
+
: [' no active intelligence readiness issues detected']),
|
|
157
|
+
' next: /intelligence review',
|
|
158
|
+
' next: /setup review',
|
|
159
|
+
].join('\n'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (sub === 'remote') {
|
|
164
|
+
const snapshot = readModels.remote.getSnapshot().supervisor;
|
|
165
|
+
const issues = snapshot.sessions.flatMap((session) => {
|
|
166
|
+
const lines: string[] = [];
|
|
167
|
+
if (session.transportState === 'degraded' || session.transportState === 'reconnecting' || session.transportState === 'terminal_failure') {
|
|
168
|
+
lines.push(`${session.runnerId}: transport=${session.transportState}`);
|
|
169
|
+
}
|
|
170
|
+
if (session.heartbeat.status !== 'fresh') {
|
|
171
|
+
lines.push(`${session.runnerId}: heartbeat=${session.heartbeat.status}`);
|
|
172
|
+
}
|
|
173
|
+
if (session.lastError) {
|
|
174
|
+
lines.push(`${session.runnerId}: ${session.lastError}`);
|
|
175
|
+
}
|
|
176
|
+
return lines;
|
|
177
|
+
});
|
|
178
|
+
ctx.print([
|
|
179
|
+
'Health Review: Remote',
|
|
180
|
+
` sessions: ${snapshot.sessions.length}`,
|
|
181
|
+
` active connections: ${snapshot.activeConnections}`,
|
|
182
|
+
` degraded: ${snapshot.degradedConnections}`,
|
|
183
|
+
...(issues.length > 0 ? issues.map((issue) => ` issue: ${issue}`) : [' no active remote recovery issues detected']),
|
|
184
|
+
' next: /remote supervisor',
|
|
185
|
+
' next: /remote recover <runnerId>',
|
|
186
|
+
].join('\n'));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (sub === 'mcp') {
|
|
191
|
+
const mcp = readModels.mcp.getSnapshot();
|
|
192
|
+
const issues = mcp.servers.flatMap((server) => {
|
|
193
|
+
const lines: string[] = [];
|
|
194
|
+
if (server.status !== 'connected' && server.status !== 'configured') {
|
|
195
|
+
lines.push(`${server.name}: status=${server.status}`);
|
|
196
|
+
}
|
|
197
|
+
if (server.schemaFreshness !== 'fresh') {
|
|
198
|
+
lines.push(`${server.name}: schema=${server.schemaFreshness}`);
|
|
199
|
+
}
|
|
200
|
+
if (server.lastError) {
|
|
201
|
+
lines.push(`${server.name}: ${server.lastError}`);
|
|
202
|
+
}
|
|
203
|
+
return lines;
|
|
204
|
+
});
|
|
205
|
+
ctx.print([
|
|
206
|
+
'Health Review: MCP',
|
|
207
|
+
` servers: ${mcp.servers.length}`,
|
|
208
|
+
` connected: ${mcp.connectedServerNames.length}`,
|
|
209
|
+
` tools: ${mcp.availableToolCount}`,
|
|
210
|
+
` total calls: ${mcp.totalCalls}`,
|
|
211
|
+
` total errors: ${mcp.totalErrors}`,
|
|
212
|
+
...(issues.length > 0 ? issues.map((issue) => ` issue: ${issue}`) : [' no active MCP lifecycle issues detected']),
|
|
213
|
+
' next: /mcp review',
|
|
214
|
+
' next: /mcp auth-review',
|
|
215
|
+
' next: /mcp repair',
|
|
216
|
+
].join('\n'));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (sub === 'continuity') {
|
|
221
|
+
const continuity = readModels.continuity.getSnapshot();
|
|
222
|
+
const returnMode = String(ctx.platform.configManager.get('behavior.returnContextMode') ?? 'off');
|
|
223
|
+
const issues: string[] = [];
|
|
224
|
+
if (!continuity.lastSessionPointer) issues.push('no last-session pointer is recorded');
|
|
225
|
+
if (continuity.recoveryFilePresent) issues.push(`recovery file present for ${continuity.sessionId || '(unknown session)'}`);
|
|
226
|
+
if (returnMode === 'off') issues.push('return-context summaries are disabled');
|
|
227
|
+
ctx.print([
|
|
228
|
+
'Health Review: Continuity',
|
|
229
|
+
` return context mode: ${returnMode}`,
|
|
230
|
+
` last session pointer: ${continuity.lastSessionPointer ?? 'none'}`,
|
|
231
|
+
` recovery file: ${continuity.recoveryFilePresent ? 'present' : 'clear'}`,
|
|
232
|
+
...(continuity.returnContext ? [` recovery activity: ${continuity.returnContext.activityLabel}`, ` recovery status: ${continuity.returnContext.statusLabel}`] : []),
|
|
233
|
+
...(issues.length > 0 ? issues.map((issue) => ` issue: ${issue}`) : [' no active session continuity issues detected']),
|
|
234
|
+
' next: /session list',
|
|
235
|
+
' next: /session hotspots',
|
|
236
|
+
].join('\n'));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (sub === 'maintenance') {
|
|
241
|
+
const session = readModels.session.getSnapshot();
|
|
242
|
+
const providerApi = requireProviderApi(ctx);
|
|
243
|
+
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
244
|
+
const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
|
|
245
|
+
? ctx.session.conversationManager.getMessagesForLLM()
|
|
246
|
+
: [];
|
|
247
|
+
const maintenance = evaluateSessionMaintenance({
|
|
248
|
+
configManager: ctx.platform.configManager,
|
|
249
|
+
currentTokens: estimateConversationTokens(llmMessages),
|
|
250
|
+
contextWindow: currentModel?.contextWindow ?? 0,
|
|
251
|
+
messageCount: llmMessages.length,
|
|
252
|
+
sessionMemoryCount: requireSessionMemoryStore(ctx).list().length,
|
|
253
|
+
session: session.session,
|
|
254
|
+
});
|
|
255
|
+
ctx.print([
|
|
256
|
+
'Health Review: Maintenance',
|
|
257
|
+
...formatSessionMaintenanceLines(maintenance, 'guided'),
|
|
258
|
+
].join('\n'));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (sub === 'worktrees') {
|
|
263
|
+
const summary = readModels.worktrees.getSnapshot().summary;
|
|
264
|
+
const issues: string[] = [];
|
|
265
|
+
if (summary.discard > 0) issues.push(`${summary.discard} worktree(s) marked discard still tracked`);
|
|
266
|
+
if (summary.pendingCleanup > 0) issues.push(`${summary.pendingCleanup} worktree(s) awaiting cleanup`);
|
|
267
|
+
if ('kept' in summary && typeof (summary as { kept?: number }).kept === 'number' && (summary as { kept?: number }).kept! > 0) {
|
|
268
|
+
// read-model summary may include kept in some implementations; ignored in rendering below if absent
|
|
269
|
+
}
|
|
270
|
+
if (summary.paused > 0) issues.push(`${summary.paused} paused worktree(s) may need resume or merge review`);
|
|
271
|
+
ctx.print([
|
|
272
|
+
'Health Review: Worktrees',
|
|
273
|
+
` total: ${summary.total}`,
|
|
274
|
+
` active: ${summary.active}`,
|
|
275
|
+
` paused: ${summary.paused}`,
|
|
276
|
+
` discard: ${summary.discard}`,
|
|
277
|
+
` cleanup pending: ${summary.pendingCleanup}`,
|
|
278
|
+
...(issues.length > 0 ? issues.map((issue) => ` issue: ${issue}`) : [' no active worktree lifecycle issues detected']),
|
|
279
|
+
' next: /worktree review',
|
|
280
|
+
' next: /worktree recover <session|task> <id>',
|
|
281
|
+
].join('\n'));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (sub === 'repair') {
|
|
286
|
+
const domain = (args[1] ?? 'review').toLowerCase();
|
|
287
|
+
const lines = ['Health Repair'];
|
|
288
|
+
if (domain === 'settings') {
|
|
289
|
+
const settings = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
290
|
+
lines.push(' domain: settings');
|
|
291
|
+
lines.push(...(
|
|
292
|
+
settings.conflicts.length > 0
|
|
293
|
+
? [' /settingssync panel', ' /settingssync show <key>', ' /managed staged']
|
|
294
|
+
: [' no active settings repair actions suggested']
|
|
295
|
+
));
|
|
296
|
+
lines.push(' verify: /health settings');
|
|
297
|
+
} else if (domain === 'auth') {
|
|
298
|
+
const auth = requireLocalUserAuthManager(ctx).inspect();
|
|
299
|
+
lines.push(' domain: auth');
|
|
300
|
+
lines.push(...(
|
|
301
|
+
auth.bootstrapCredentialPresent
|
|
302
|
+
? [' /auth local review', ' /auth local rotate-password admin <password>', ' /auth local clear-bootstrap-file']
|
|
303
|
+
: [' /auth local review']
|
|
304
|
+
));
|
|
305
|
+
lines.push(' verify: /health auth');
|
|
306
|
+
} else if (domain === 'accounts') {
|
|
307
|
+
lines.push(' domain: accounts');
|
|
308
|
+
lines.push(' /accounts review');
|
|
309
|
+
lines.push(' /accounts routes <provider>');
|
|
310
|
+
lines.push(' /accounts repair <provider>');
|
|
311
|
+
lines.push(' /auth show <provider>');
|
|
312
|
+
lines.push(' verify: /health accounts');
|
|
313
|
+
} else if (domain === 'services') {
|
|
314
|
+
lines.push(' domain: services');
|
|
315
|
+
lines.push(' /services doctor');
|
|
316
|
+
lines.push(' /services auth-review');
|
|
317
|
+
lines.push(' /health services');
|
|
318
|
+
lines.push(' verify: /health services');
|
|
319
|
+
} else if (domain === 'sandbox') {
|
|
320
|
+
lines.push(' domain: sandbox');
|
|
321
|
+
lines.push(' /sandbox');
|
|
322
|
+
lines.push(' delegate sandbox/QEMU setup and execution to GoodVibes TUI');
|
|
323
|
+
lines.push(' /health sandbox');
|
|
324
|
+
lines.push(' verify: /health sandbox');
|
|
325
|
+
} else if (domain === 'remote') {
|
|
326
|
+
lines.push(' domain: remote');
|
|
327
|
+
lines.push(' /remote supervisor');
|
|
328
|
+
lines.push(' /remote recover <runnerId>');
|
|
329
|
+
lines.push(' /remote setup');
|
|
330
|
+
lines.push(' verify: /health remote');
|
|
331
|
+
} else if (domain === 'mcp') {
|
|
332
|
+
lines.push(' domain: mcp');
|
|
333
|
+
lines.push(' /mcp review');
|
|
334
|
+
lines.push(' /mcp auth-review');
|
|
335
|
+
lines.push(' /mcp repair [server]');
|
|
336
|
+
lines.push(' verify: /health mcp');
|
|
337
|
+
} else if (domain === 'continuity') {
|
|
338
|
+
lines.push(' domain: continuity');
|
|
339
|
+
lines.push(' /session list');
|
|
340
|
+
lines.push(' /session resume <id>');
|
|
341
|
+
lines.push(' /session hotspots');
|
|
342
|
+
lines.push(' verify: /health continuity');
|
|
343
|
+
} else if (domain === 'maintenance') {
|
|
344
|
+
lines.push(' domain: maintenance');
|
|
345
|
+
lines.push(' /health maintenance');
|
|
346
|
+
lines.push(' /guidance review');
|
|
347
|
+
lines.push(' /compact');
|
|
348
|
+
lines.push(' /panel tokens');
|
|
349
|
+
lines.push(' verify: /health maintenance');
|
|
350
|
+
} else if (domain === 'worktrees') {
|
|
351
|
+
lines.push(' domain: worktrees');
|
|
352
|
+
lines.push(' /worktree review');
|
|
353
|
+
lines.push(' /worktree recover <session|task> <id>');
|
|
354
|
+
lines.push(' verify: /health worktrees');
|
|
355
|
+
} else if (domain === 'intelligence') {
|
|
356
|
+
lines.push(' domain: intelligence');
|
|
357
|
+
lines.push(' /intelligence review');
|
|
358
|
+
lines.push(' /intelligence symbols <file>');
|
|
359
|
+
lines.push(' /intelligence definition <file> <line> <column>');
|
|
360
|
+
lines.push(' /setup review');
|
|
361
|
+
lines.push(' verify: /health intelligence');
|
|
362
|
+
} else {
|
|
363
|
+
lines.push(' domains: settings, auth, accounts, services, sandbox, remote, mcp, continuity, maintenance, worktrees, intelligence');
|
|
364
|
+
lines.push(' use: /health repair <domain>');
|
|
365
|
+
}
|
|
366
|
+
ctx.print(lines.join('\n'));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const session = readModels.session.getSnapshot();
|
|
371
|
+
const providerApi = requireProviderApi(ctx);
|
|
372
|
+
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
373
|
+
const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
|
|
374
|
+
? ctx.session.conversationManager.getMessagesForLLM()
|
|
375
|
+
: [];
|
|
376
|
+
const contextWindow = currentModel?.contextWindow ?? 0;
|
|
377
|
+
const maintenance = evaluateSessionMaintenance({
|
|
378
|
+
configManager: ctx.platform.configManager,
|
|
379
|
+
currentTokens: estimateConversationTokens(llmMessages),
|
|
380
|
+
contextWindow,
|
|
381
|
+
messageCount: llmMessages.length,
|
|
382
|
+
sessionMemoryCount: requireSessionMemoryStore(ctx).list().length,
|
|
383
|
+
session: session.session,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const snapshot = await buildSetupReviewSnapshot(ctx);
|
|
387
|
+
const operatorClient = requireOperatorClient(ctx);
|
|
388
|
+
const accountSnapshot = await operatorClient.providers.accountSnapshot();
|
|
389
|
+
const settingsSnapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
390
|
+
if (sub === 'setup') {
|
|
391
|
+
ctx.print([
|
|
392
|
+
'Health Review: Setup',
|
|
393
|
+
...snapshot.issues.map((issue) => ` [${issue.severity.toUpperCase()}] ${issue.area}: ${issue.message}`),
|
|
394
|
+
...(snapshot.serviceIssues.length > 0
|
|
395
|
+
? ['', ' Service issues:', ...snapshot.serviceIssues.map((issue) => ` - ${issue}`)]
|
|
396
|
+
: []),
|
|
397
|
+
].join('\n'));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
ctx.print([
|
|
402
|
+
'Health Review',
|
|
403
|
+
` session: ${snapshot.sessionId}`,
|
|
404
|
+
` setup issues: ${snapshot.issues.length}`,
|
|
405
|
+
` service issues: ${snapshot.serviceIssues.length}`,
|
|
406
|
+
` active subscriptions: ${snapshot.activeSubscriptionCount}`,
|
|
407
|
+
` account issues: ${accountSnapshot.issueCount}`,
|
|
408
|
+
` settings conflicts: ${settingsSnapshot.conflicts.length}`,
|
|
409
|
+
` managed locks: ${settingsSnapshot.managedLockCount}`,
|
|
410
|
+
` local auth users: ${readModels.localAuth.getSnapshot().userCount}`,
|
|
411
|
+
` remote runners: ${snapshot.remoteRunnerCount}`,
|
|
412
|
+
...renderSandboxHealthSummary(ctx.platform.configManager),
|
|
413
|
+
'',
|
|
414
|
+
...formatSessionMaintenanceLines(maintenance, 'guided').map((line) => ` ${line}`),
|
|
415
|
+
...(snapshot.issues.length > 0 ? ['', ...snapshot.issues.map((issue) => ` [${issue.severity.toUpperCase()}] ${issue.area}: ${issue.message}`)] : []),
|
|
416
|
+
...(snapshot.serviceIssues.length > 0 ? ['', ...snapshot.serviceIssues.map((issue) => ` service: ${issue}`)] : []),
|
|
417
|
+
'',
|
|
418
|
+
'Next steps:',
|
|
419
|
+
' /health open',
|
|
420
|
+
' /health services',
|
|
421
|
+
' /health sandbox',
|
|
422
|
+
' /health accounts',
|
|
423
|
+
' /health auth',
|
|
424
|
+
' /health settings',
|
|
425
|
+
' /health intelligence',
|
|
426
|
+
' /health remote',
|
|
427
|
+
' /health maintenance',
|
|
428
|
+
' /health worktrees',
|
|
429
|
+
' /health repair <domain>',
|
|
430
|
+
' /setup onboarding',
|
|
431
|
+
].join('\n'));
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import { requireHookApi } from './runtime-services.ts';
|
|
3
|
+
|
|
4
|
+
export function registerHooksRuntimeCommands(registry: CommandRegistry): void {
|
|
5
|
+
registry.register({
|
|
6
|
+
name: 'hooks',
|
|
7
|
+
aliases: [],
|
|
8
|
+
description: 'Inspect, author, simulate, and reload managed hook workflows',
|
|
9
|
+
usage: '[contracts [filter] | reload | scaffold <name> <match> <type> | chain <name> <event1,event2,...> | remove <name> | enable <name> | disable <name> | simulate <eventPath> | inspect <path> | import <path> [merge|replace] | export [path]]',
|
|
10
|
+
argsHint: '[subcommand]',
|
|
11
|
+
async handler(args, ctx) {
|
|
12
|
+
const hookApi = requireHookApi(ctx);
|
|
13
|
+
const workbench = hookApi.workbench;
|
|
14
|
+
if (args.length === 0 && ctx.openHooksPanel) {
|
|
15
|
+
ctx.openHooksPanel();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const subcommand = (args[0] ?? 'contracts').toLowerCase();
|
|
20
|
+
if (subcommand === 'reload') {
|
|
21
|
+
await workbench.reload();
|
|
22
|
+
ctx.print(`Reloaded managed hooks from ${workbench.getFilePath()}`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (subcommand === 'scaffold') {
|
|
26
|
+
const [name, match, type] = args.slice(1);
|
|
27
|
+
if (!name || !match || !type) {
|
|
28
|
+
ctx.print('Usage: /hooks scaffold <name> <match> <command|prompt|agent|http|ts>');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!['command', 'prompt', 'agent', 'http', 'ts'].includes(type)) {
|
|
32
|
+
ctx.print(`Unknown hook type: ${type}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const hook = await workbench.scaffoldHook(name, match, type as Parameters<typeof workbench.scaffoldHook>[2]);
|
|
36
|
+
ctx.print(`Scaffolded managed hook ${hook.name} at ${match} in ${workbench.getFilePath()}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (subcommand === 'chain') {
|
|
40
|
+
const name = args[1];
|
|
41
|
+
const matches = args[2]?.split(',').map((entry) => entry.trim()).filter(Boolean) ?? [];
|
|
42
|
+
if (!name || matches.length === 0) {
|
|
43
|
+
ctx.print('Usage: /hooks chain <name> <event1,event2,...>');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const chain = await workbench.scaffoldChain(name, matches);
|
|
47
|
+
ctx.print(`Scaffolded managed hook chain ${chain.name} with ${chain.steps.length} step(s).`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (subcommand === 'remove') {
|
|
51
|
+
const name = args[1];
|
|
52
|
+
if (!name) {
|
|
53
|
+
ctx.print('Usage: /hooks remove <name>');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const removed = await workbench.remove(name);
|
|
57
|
+
if (!removed) {
|
|
58
|
+
ctx.print(`No managed hook or chain named ${name}.`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
ctx.print(`Removed managed hook workflow entry ${name}.`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (subcommand === 'enable' || subcommand === 'disable') {
|
|
65
|
+
const name = args[1];
|
|
66
|
+
if (!name) {
|
|
67
|
+
ctx.print(`Usage: /hooks ${subcommand} <name>`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const changed = await workbench.toggle(name, subcommand === 'enable');
|
|
71
|
+
if (!changed) {
|
|
72
|
+
ctx.print(`No managed hook named ${name}.`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
ctx.print(`${subcommand === 'enable' ? 'Enabled' : 'Disabled'} managed hook ${name}.`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (subcommand === 'simulate') {
|
|
79
|
+
const eventPath = args[1];
|
|
80
|
+
if (!eventPath) {
|
|
81
|
+
ctx.print('Usage: /hooks simulate <eventPath>');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const result = workbench.simulate(eventPath);
|
|
85
|
+
ctx.print([
|
|
86
|
+
`Hook simulation for ${result.eventPath}`,
|
|
87
|
+
` matched hooks: ${result.matchedHooks.length}`,
|
|
88
|
+
...result.matchedHooks.map((entry) => ` ${entry.name} ${entry.pattern} ${entry.type}`),
|
|
89
|
+
` matched chains: ${result.matchedChains.length}`,
|
|
90
|
+
...result.matchedChains.map((entry) => ` ${entry.name} stepMatches=${entry.stepMatches}`),
|
|
91
|
+
].join('\n'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (subcommand === 'export') {
|
|
95
|
+
const path = await workbench.export(args[1] ?? workbench.getFilePath());
|
|
96
|
+
ctx.print(`Exported managed hooks to ${path}`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (subcommand === 'inspect') {
|
|
100
|
+
const path = args[1];
|
|
101
|
+
if (!path) {
|
|
102
|
+
ctx.print('Usage: /hooks inspect <path>');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const inspection = workbench.inspect(path);
|
|
106
|
+
ctx.print([
|
|
107
|
+
`Hook bundle inspection: ${inspection.path}`,
|
|
108
|
+
` hooks: ${inspection.hookCount}`,
|
|
109
|
+
` chains: ${inspection.chainCount}`,
|
|
110
|
+
` patterns: ${inspection.patterns.join(', ') || '(none)'}`,
|
|
111
|
+
].join('\n'));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (subcommand === 'import') {
|
|
115
|
+
const path = args[1];
|
|
116
|
+
const strategy = args[2] === 'replace' ? 'replace' : 'merge';
|
|
117
|
+
if (!path) {
|
|
118
|
+
ctx.print('Usage: /hooks import <path> [merge|replace]');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await workbench.import(path, strategy);
|
|
122
|
+
ctx.print(`Imported managed hooks from ${path} using ${strategy} strategy.`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const filter = (subcommand === 'contracts' ? args.slice(1) : args).join(' ').trim().toLowerCase();
|
|
127
|
+
const contracts = hookApi.contracts(filter);
|
|
128
|
+
|
|
129
|
+
if (contracts.length === 0) {
|
|
130
|
+
ctx.print(filter.length === 0 ? 'No hook contracts registered.' : `No hook contracts matched "${filter}".`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const lines: string[] = [`Hook Contracts (${contracts.length}):`];
|
|
135
|
+
for (const contract of contracts) {
|
|
136
|
+
lines.push(` ${contract.pattern}`);
|
|
137
|
+
lines.push(` authority=${contract.authority} mode=${contract.executionMode} deny=${contract.canDeny ? 'yes' : 'no'} mutate=${contract.canMutateInput ? 'yes' : 'no'} inject=${contract.canInjectContext ? 'yes' : 'no'} timeout=${contract.timeoutMs}ms policy=${contract.failurePolicy}`);
|
|
138
|
+
lines.push(` ${contract.description}`);
|
|
139
|
+
}
|
|
140
|
+
const managedHooks = workbench.listManagedHooks();
|
|
141
|
+
const managedChains = workbench.listManagedChains();
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push(`Managed hooks file: ${workbench.getFilePath()}`);
|
|
144
|
+
lines.push(`Managed entries: hooks=${managedHooks.length} chains=${managedChains.length}`);
|
|
145
|
+
ctx.print(lines.join('\n'));
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { dirname, resolve } from 'path';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
|
+
import { buildIncidentMemoryAddOptions } from '@pellux/goodvibes-sdk/platform/state';
|
|
5
|
+
import { requireShellPaths } from './runtime-services.ts';
|
|
6
|
+
import { getMemoryApi } from './recall-query.ts';
|
|
7
|
+
|
|
8
|
+
export function registerIncidentRuntimeCommands(registry: CommandRegistry): void {
|
|
9
|
+
registry.register({
|
|
10
|
+
name: 'incident',
|
|
11
|
+
aliases: [],
|
|
12
|
+
description: 'Open, export, and capture incident review bundles',
|
|
13
|
+
usage: '[open | latest | show <id|latest> | export <id|latest> <path> | capture <id|latest>]',
|
|
14
|
+
async handler(args, ctx) {
|
|
15
|
+
const shellPaths = requireShellPaths(ctx);
|
|
16
|
+
const subcommand = (args[0] ?? 'open').toLowerCase();
|
|
17
|
+
const forensicRegistry = ctx.extensions.forensicsRegistry;
|
|
18
|
+
if (subcommand === 'open') {
|
|
19
|
+
if (ctx.openIncidentPanel) {
|
|
20
|
+
ctx.openIncidentPanel();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
ctx.print('Incident panel is not available in this runtime.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!forensicRegistry) {
|
|
27
|
+
ctx.print('Forensics registry is not available in this runtime.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const requestedId = args[1];
|
|
31
|
+
const report = !requestedId || requestedId === 'latest'
|
|
32
|
+
? forensicRegistry.latest()
|
|
33
|
+
: forensicRegistry.getById(requestedId);
|
|
34
|
+
if (subcommand === 'latest' || subcommand === 'show') {
|
|
35
|
+
if (!report) {
|
|
36
|
+
ctx.print('No incident bundle is available.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const bundle = forensicRegistry.buildBundle(report.id);
|
|
40
|
+
if (!bundle) {
|
|
41
|
+
ctx.print(`Failed to build incident bundle for ${report.id}.`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
ctx.print([
|
|
45
|
+
`Incident ${report.id}`,
|
|
46
|
+
` classification: ${report.classification}`,
|
|
47
|
+
` summary: ${report.summary}`,
|
|
48
|
+
` root cause: ${bundle.evidence.rootCause ?? 'n/a'}`,
|
|
49
|
+
` denied permissions: ${bundle.evidence.deniedPermissionCount}`,
|
|
50
|
+
` budget breaches: ${bundle.evidence.budgetBreachCount}`,
|
|
51
|
+
` replay mismatches: ${bundle.replay.mismatchCount}`,
|
|
52
|
+
].join('\n'));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (subcommand === 'export') {
|
|
56
|
+
const pathArg = args[2];
|
|
57
|
+
if (!requestedId || !pathArg) {
|
|
58
|
+
ctx.print('Usage: /incident export <id|latest> <path>');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!report) {
|
|
62
|
+
ctx.print(`Incident not found: ${requestedId}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const bundleJson = forensicRegistry.exportBundleAsJson(report.id);
|
|
66
|
+
if (!bundleJson) {
|
|
67
|
+
ctx.print(`Failed to export incident bundle for ${report.id}.`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
71
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
72
|
+
writeFileSync(targetPath, `${bundleJson}\n`, 'utf-8');
|
|
73
|
+
ctx.print(`Exported incident bundle ${report.id} to ${targetPath}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (subcommand === 'capture') {
|
|
77
|
+
const memory = getMemoryApi(ctx);
|
|
78
|
+
if (!memory) return;
|
|
79
|
+
if (!report) {
|
|
80
|
+
ctx.print(`Incident not found: ${requestedId ?? 'latest'}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const bundle = forensicRegistry.buildBundle(report.id);
|
|
84
|
+
if (!bundle) {
|
|
85
|
+
ctx.print(`Failed to build incident bundle for ${report.id}.`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const record = await memory.add(buildIncidentMemoryAddOptions(bundle));
|
|
89
|
+
ctx.print(`Captured incident ${report.id} into durable memory as ${record.id}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
ctx.print('Usage: /incident [open | latest | show <id|latest> | export <id|latest> <path> | capture <id|latest>]');
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|