@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,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /session command handler — Multi-session Orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Implements session and workflow commands:
|
|
5
|
+
*
|
|
6
|
+
* /session link-task <taskId> [--session <sessionId>] [--depends-on <ref>] [--label <label>]
|
|
7
|
+
* — Register a task as a global cross-session ref, optionally linking it to a
|
|
8
|
+
* dependency.
|
|
9
|
+
*
|
|
10
|
+
* /session handoff <taskId> --to <sessionId> [--session <sessionId>] [--reason <reason>]
|
|
11
|
+
* — Initiate a task handoff from the current session to another.
|
|
12
|
+
*
|
|
13
|
+
* /session graph [--session <sessionId>] [--format text|json]
|
|
14
|
+
* — Display the cross-session task dependency graph.
|
|
15
|
+
*
|
|
16
|
+
* /session cancel <taskId|--scope session> [--session <sessionId>] [--scope task|subtree|session]
|
|
17
|
+
* — Cancel tasks with configurable scope semantics.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
21
|
+
import type { CancellationScope, CrossSessionTaskRef } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
22
|
+
import { VALID_SCOPES } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
23
|
+
import { handleSessionWorkflowCommand } from './session-workflow.ts';
|
|
24
|
+
import { requireSessionOrchestration } from './runtime-services.ts';
|
|
25
|
+
|
|
26
|
+
// ── Argument parsing helpers ──────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract a named flag value from args (e.g. `--to <value>`).
|
|
30
|
+
* Returns undefined if the flag is not present.
|
|
31
|
+
*/
|
|
32
|
+
function flagValue(args: string[], flag: string): string | undefined {
|
|
33
|
+
const idx = args.indexOf(flag);
|
|
34
|
+
if (idx === -1 || idx + 1 >= args.length) return undefined;
|
|
35
|
+
return args[idx + 1];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse a cross-session task ref from a string of the form
|
|
40
|
+
* `<sessionId>:<taskId>` or just `<taskId>` (uses currentSessionId as owner).
|
|
41
|
+
*/
|
|
42
|
+
function parseRef(
|
|
43
|
+
raw: string,
|
|
44
|
+
currentSessionId: string,
|
|
45
|
+
): { sessionId: string; taskId: string } {
|
|
46
|
+
const parts = raw.split(':', 2);
|
|
47
|
+
if (parts.length === 2) {
|
|
48
|
+
return { sessionId: parts[0]!, taskId: parts[1]! };
|
|
49
|
+
}
|
|
50
|
+
return { sessionId: currentSessionId, taskId: raw };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Formatting helpers ────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/** Status badge for display. */
|
|
56
|
+
function statusBadge(status: string): string {
|
|
57
|
+
switch (status) {
|
|
58
|
+
case 'queued': return '[ ]';
|
|
59
|
+
case 'running': return '[>]';
|
|
60
|
+
case 'blocked': return '[~]';
|
|
61
|
+
case 'completed': return '[+]';
|
|
62
|
+
case 'failed': return '[!]';
|
|
63
|
+
case 'cancelled': return '[x]';
|
|
64
|
+
default: return '[?]';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Format a ref as a compact one-line string. */
|
|
69
|
+
function fmtRef(ref: CrossSessionTaskRef): string {
|
|
70
|
+
const key = `${ref.sessionId.slice(0, 8)}...:${ref.taskId.slice(0, 8)}...`;
|
|
71
|
+
const label = ref.label ? ` [${ref.label}]` : '';
|
|
72
|
+
return `${statusBadge(ref.status)} ${key}${label} "${ref.title}"`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── /session link-task ────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
function handleLinkTask(args: string[], context: CommandContext): void {
|
|
78
|
+
const taskId = args[0];
|
|
79
|
+
if (!taskId) {
|
|
80
|
+
context.print(
|
|
81
|
+
'[session] Usage: /session link-task <taskId> [--session <sessionId>] ' +
|
|
82
|
+
'[--depends-on <sessionId:taskId>] [--label <label>]',
|
|
83
|
+
);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Defense-in-depth: parser splits on whitespace but guard against future changes
|
|
88
|
+
if (!taskId.trim()) {
|
|
89
|
+
context.print('Error: taskId cannot be empty or whitespace.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (taskId.includes(':')) {
|
|
94
|
+
context.print('Error: taskId cannot contain ":" — it conflicts with the composite key format.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const sessionId = flagValue(args, '--session') ?? context.session.runtime.sessionId;
|
|
99
|
+
const dependsOnRaw = flagValue(args, '--depends-on');
|
|
100
|
+
const label = flagValue(args, '--label');
|
|
101
|
+
|
|
102
|
+
const orchestration = requireSessionOrchestration(context);
|
|
103
|
+
|
|
104
|
+
const ref: CrossSessionTaskRef = {
|
|
105
|
+
sessionId,
|
|
106
|
+
taskId,
|
|
107
|
+
title: label ?? taskId,
|
|
108
|
+
status: 'queued',
|
|
109
|
+
createdAt: Date.now(),
|
|
110
|
+
updatedAt: Date.now(),
|
|
111
|
+
label,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const dependsOn = dependsOnRaw ? parseRef(dependsOnRaw, sessionId) : undefined;
|
|
115
|
+
|
|
116
|
+
const result = orchestration.linkTask(ref, dependsOn);
|
|
117
|
+
|
|
118
|
+
if (!result.ok) {
|
|
119
|
+
context.print(`[session] link-task failed: ${result.error}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
context.print(
|
|
124
|
+
`[session] Task linked: ${sessionId.slice(0, 8)}...:${taskId}` +
|
|
125
|
+
(label ? ` [${label}]` : '') +
|
|
126
|
+
(dependsOn ? ` → depends on ${dependsOnRaw}` : ''),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── /session handoff ──────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
function handleHandoff(args: string[], context: CommandContext): void {
|
|
133
|
+
const taskId = args[0];
|
|
134
|
+
const toSessionId = flagValue(args, '--to');
|
|
135
|
+
const fromSessionId = flagValue(args, '--session') ?? context.session.runtime.sessionId;
|
|
136
|
+
const reason = flagValue(args, '--reason');
|
|
137
|
+
|
|
138
|
+
if (!taskId || !toSessionId) {
|
|
139
|
+
context.print(
|
|
140
|
+
'[session] Usage: /session handoff <taskId> --to <sessionId> ' +
|
|
141
|
+
'[--session <fromSessionId>] [--reason <reason>]',
|
|
142
|
+
);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const orchestration = requireSessionOrchestration(context);
|
|
147
|
+
|
|
148
|
+
const result = orchestration.initiateHandoff(
|
|
149
|
+
{ sessionId: fromSessionId, taskId },
|
|
150
|
+
fromSessionId,
|
|
151
|
+
toSessionId,
|
|
152
|
+
reason,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (!result.ok) {
|
|
156
|
+
context.print(`[session] handoff failed: ${result.error}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
context.print(
|
|
161
|
+
`[session] Handoff initiated: ${taskId} (${fromSessionId.slice(0, 8)}...) → (${toSessionId.slice(0, 8)}...)` +
|
|
162
|
+
(reason ? ` reason: ${reason}` : '') +
|
|
163
|
+
`\n[session] handoffId: ${result.handoffId}`,
|
|
164
|
+
);
|
|
165
|
+
context.print(
|
|
166
|
+
'[session] The task is now blocked pending acknowledgement from the destination session.',
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── /session graph ────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
function handleGraph(args: string[], context: CommandContext): void {
|
|
173
|
+
const filterSession = flagValue(args, '--session');
|
|
174
|
+
const format = flagValue(args, '--format') ?? 'text';
|
|
175
|
+
|
|
176
|
+
const orchestration = requireSessionOrchestration(context);
|
|
177
|
+
const snap = orchestration.snapshot();
|
|
178
|
+
|
|
179
|
+
const refs = Object.values(snap.refs);
|
|
180
|
+
const filteredRefs = filterSession
|
|
181
|
+
? refs.filter((r) => r.sessionId === filterSession)
|
|
182
|
+
: refs;
|
|
183
|
+
|
|
184
|
+
if (format === 'json') {
|
|
185
|
+
context.print(JSON.stringify(snap, null, 2));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── Text display ─────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
if (filteredRefs.length === 0) {
|
|
192
|
+
if (filterSession) {
|
|
193
|
+
context.print(`[session] No tasks registered for session ${filterSession.slice(0, 8)}...`);
|
|
194
|
+
} else {
|
|
195
|
+
context.print('[session] Task graph is empty. Use /session link-task to register tasks.');
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const lines: string[] = [
|
|
201
|
+
`[session] Cross-session task graph (${filteredRefs.length} task${filteredRefs.length !== 1 ? 's' : ''}):`,
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
// Group by session for readability
|
|
205
|
+
const bySession = new Map<string, CrossSessionTaskRef[]>();
|
|
206
|
+
for (const ref of filteredRefs) {
|
|
207
|
+
const group = bySession.get(ref.sessionId) ?? [];
|
|
208
|
+
group.push(ref);
|
|
209
|
+
bySession.set(ref.sessionId, group);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (const [sid, sessionRefs] of bySession) {
|
|
213
|
+
lines.push(` Session ${sid.slice(0, 16)}...`);
|
|
214
|
+
for (const ref of sessionRefs) {
|
|
215
|
+
lines.push(` ${fmtRef(ref)}`);
|
|
216
|
+
|
|
217
|
+
// Show dependencies
|
|
218
|
+
const deps = orchestration.getDependencies(ref.sessionId, ref.taskId);
|
|
219
|
+
for (const dep of deps) {
|
|
220
|
+
lines.push(
|
|
221
|
+
` depends-on: ${statusBadge(dep.status)} ${dep.sessionId.slice(0, 8)}...:${dep.taskId.slice(0, 8)}... "${dep.title}"`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Show dependents
|
|
226
|
+
const dependents = orchestration.getDependents(ref.sessionId, ref.taskId);
|
|
227
|
+
for (const d of dependents) {
|
|
228
|
+
lines.push(
|
|
229
|
+
` depended-by: ${statusBadge(d.status)} ${d.sessionId.slice(0, 8)}...:${d.taskId.slice(0, 8)}... "${d.title}"`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Handoffs summary
|
|
236
|
+
const handoffs = orchestration.getHandoffs();
|
|
237
|
+
const filteredHandoffs = filterSession
|
|
238
|
+
? handoffs.filter((h) => h.fromSessionId === filterSession || h.toSessionId === filterSession)
|
|
239
|
+
: handoffs;
|
|
240
|
+
|
|
241
|
+
if (filteredHandoffs.length > 0) {
|
|
242
|
+
lines.push(` Handoffs (${filteredHandoffs.length}):`);
|
|
243
|
+
for (const h of filteredHandoffs) {
|
|
244
|
+
const ack = h.acknowledged ? 'ack' : 'pending';
|
|
245
|
+
lines.push(
|
|
246
|
+
` ${h.handoffId.slice(0, 8)}... ` +
|
|
247
|
+
`${h.fromSessionId.slice(0, 8)}... → ${h.toSessionId.slice(0, 8)}... ` +
|
|
248
|
+
`task:${h.taskRef.taskId.slice(0, 8)}... [${ack}]` +
|
|
249
|
+
(h.reason ? ` reason: ${h.reason}` : ''),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
context.print(lines.join('\n'));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── /session cancel ───────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
function handleCancel(args: string[], context: CommandContext): void {
|
|
260
|
+
const scopeRaw = flagValue(args, '--scope');
|
|
261
|
+
if (scopeRaw && !VALID_SCOPES.includes(scopeRaw as CancellationScope)) {
|
|
262
|
+
context.print(`Invalid --scope: "${scopeRaw}". Valid: ${VALID_SCOPES.join(', ')}`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const scope: CancellationScope = (scopeRaw as CancellationScope) ?? 'task';
|
|
266
|
+
const sessionId = flagValue(args, '--session') ?? context.session.runtime.sessionId;
|
|
267
|
+
const reason = flagValue(args, '--reason');
|
|
268
|
+
|
|
269
|
+
// For session scope, taskId is not required
|
|
270
|
+
const taskId = scope === 'session' ? undefined : args[0];
|
|
271
|
+
|
|
272
|
+
if (scope !== 'session' && !taskId) {
|
|
273
|
+
context.print(
|
|
274
|
+
'[session] Usage: /session cancel <taskId> [--scope task|subtree|session] ' +
|
|
275
|
+
'[--session <sessionId>] [--reason <reason>]\n' +
|
|
276
|
+
' --scope task Cancel only this task (default)\n' +
|
|
277
|
+
' --scope subtree Cancel this task and all tasks that transitively depend on it\n' +
|
|
278
|
+
' --scope session Cancel all tasks in the session',
|
|
279
|
+
);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const orchestration = requireSessionOrchestration(context);
|
|
284
|
+
|
|
285
|
+
const result = orchestration.cancel({
|
|
286
|
+
sessionId,
|
|
287
|
+
taskId,
|
|
288
|
+
scope,
|
|
289
|
+
reason,
|
|
290
|
+
requestedAt: Date.now(),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (!result.ok) {
|
|
294
|
+
context.print(`[session] cancel failed: ${result.error}`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const lines: string[] = [
|
|
299
|
+
`[session] Cancelled ${result.cancelled.length} task${result.cancelled.length !== 1 ? 's' : ''} ` +
|
|
300
|
+
`(scope=${scope}):`,
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
for (const t of result.cancelled) {
|
|
304
|
+
lines.push(` [x] ${t.sessionId.slice(0, 8)}...:${t.taskId.slice(0, 8)}... "${t.title}"`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (result.skipped.length > 0) {
|
|
308
|
+
lines.push(` Skipped ${result.skipped.length} (already terminal):`);
|
|
309
|
+
for (const s of result.skipped) {
|
|
310
|
+
lines.push(` [-] ${s.sessionId.slice(0, 8)}...:${s.taskId.slice(0, 8)}... "${s.title}" (${s.reason})`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
context.print(lines.join('\n'));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ── Top-level command definition ───────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* sessionCommand — The `/session` slash command.
|
|
321
|
+
*
|
|
322
|
+
* Routes to multi-session orchestration subcommand handlers based on args[0].
|
|
323
|
+
*/
|
|
324
|
+
export const sessionCommand: SlashCommand = {
|
|
325
|
+
name: 'session',
|
|
326
|
+
aliases: ['sess'],
|
|
327
|
+
description: 'Multi-session orchestration: link tasks, handoff, view graph, and cancel across sessions.',
|
|
328
|
+
usage: '<subcommand> [args]',
|
|
329
|
+
argsHint: 'link-task|handoff|graph|cancel',
|
|
330
|
+
handler: async (args: string[], context: CommandContext): Promise<void> => {
|
|
331
|
+
const [sub, ...rest] = args;
|
|
332
|
+
|
|
333
|
+
switch (sub) {
|
|
334
|
+
case 'link-task':
|
|
335
|
+
case 'link':
|
|
336
|
+
handleLinkTask(rest, context);
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
case 'handoff':
|
|
340
|
+
case 'ho':
|
|
341
|
+
handleHandoff(rest, context);
|
|
342
|
+
break;
|
|
343
|
+
|
|
344
|
+
case 'graph':
|
|
345
|
+
case 'g':
|
|
346
|
+
handleGraph(rest, context);
|
|
347
|
+
break;
|
|
348
|
+
|
|
349
|
+
case 'cancel':
|
|
350
|
+
handleCancel(rest, context);
|
|
351
|
+
break;
|
|
352
|
+
|
|
353
|
+
default: {
|
|
354
|
+
const handled = await handleSessionWorkflowCommand(args, context);
|
|
355
|
+
if (!handled) {
|
|
356
|
+
const usage = [
|
|
357
|
+
'Usage: /session <subcommand>',
|
|
358
|
+
' list | rename <name> | resume <id|name> | fork [name] | save [name] | info [id] | export <id> [format] | search <query> | delete <id>',
|
|
359
|
+
' — Session continuity, export, resume, and pruning',
|
|
360
|
+
' link-task <taskId> [--session <sid>] [--depends-on <sid:taskId>] [--label <label>]',
|
|
361
|
+
' — Register a task in the cross-session graph',
|
|
362
|
+
' handoff <taskId> --to <sid> [--session <sid>] [--reason <reason>]',
|
|
363
|
+
' — Hand a task off to another session',
|
|
364
|
+
' graph [--session <sid>] [--format text|json]',
|
|
365
|
+
' — Display the cross-session task dependency graph',
|
|
366
|
+
' cancel <taskId> [--scope task|subtree|session] [--session <sid>] [--reason <reason>]',
|
|
367
|
+
' — Cancel tasks with scoped semantics',
|
|
368
|
+
].join('\n');
|
|
369
|
+
context.print(usage);
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
applySettingsSyncBundle,
|
|
5
|
+
clearManagedSettingLock,
|
|
6
|
+
exportSettingsSyncBundle,
|
|
7
|
+
formatResolvedSettingReview,
|
|
8
|
+
resolveSettingsSyncConflict,
|
|
9
|
+
formatStagedManagedBundleReview,
|
|
10
|
+
formatSettingsControlPlaneReview,
|
|
11
|
+
getSettingsControlPlaneSnapshot,
|
|
12
|
+
inspectSettingsSyncBundle,
|
|
13
|
+
recordSettingsSyncEvent,
|
|
14
|
+
recordSettingsSyncFailure,
|
|
15
|
+
setManagedSettingLock,
|
|
16
|
+
type SettingsSyncBundle,
|
|
17
|
+
} from '@/runtime/index.ts';
|
|
18
|
+
import { getProviderIdFromModel } from '../../config/provider-model.ts';
|
|
19
|
+
import { type ConfigKey } from '../../config/index.ts';
|
|
20
|
+
import { CONFIG_KEYS } from '@pellux/goodvibes-sdk/platform/config';
|
|
21
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
22
|
+
import { openCommandPanel, requireShellPaths } from './runtime-services.ts';
|
|
23
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
24
|
+
|
|
25
|
+
export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry): void {
|
|
26
|
+
registry.register({
|
|
27
|
+
name: 'settingssync',
|
|
28
|
+
aliases: ['settings-sync'],
|
|
29
|
+
description: 'Review sync posture, export/import settings-sync bundles, and open the settings sync workspace',
|
|
30
|
+
usage: '[review|panel|show <key>|staged|conflicts|resolve <key> <local|synced>|failures|rollback-history|export <path>|inspect <path>|pull <path>|push <path>|lock <key> <source> <reason...>|unlock <key>]',
|
|
31
|
+
handler(args, ctx) {
|
|
32
|
+
const shellPaths = requireShellPaths(ctx);
|
|
33
|
+
const controlPlaneConfigDir = ctx.platform.configManager.getControlPlaneConfigDir();
|
|
34
|
+
const sub = (args[0] ?? 'review').toLowerCase();
|
|
35
|
+
if (sub === 'panel' || sub === 'open') {
|
|
36
|
+
openCommandPanel(ctx, 'settings-sync');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (sub === 'show') {
|
|
40
|
+
const key = args[1] as ConfigKey | undefined;
|
|
41
|
+
if (!key || !CONFIG_KEYS.has(key)) {
|
|
42
|
+
ctx.print('Usage: /settingssync show <config-key>');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
ctx.print(formatResolvedSettingReview(ctx.platform.configManager, key));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (sub === 'staged') {
|
|
49
|
+
ctx.print(formatStagedManagedBundleReview(ctx.platform.configManager));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (sub === 'conflicts') {
|
|
53
|
+
const snapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
54
|
+
ctx.print(snapshot.conflicts.length > 0
|
|
55
|
+
? [
|
|
56
|
+
'Settings Sync Conflicts',
|
|
57
|
+
...snapshot.conflicts.map((conflict) => ` ${conflict.key} source=${conflict.source} path=${conflict.path}`),
|
|
58
|
+
].join('\n')
|
|
59
|
+
: 'Settings Sync Conflicts\n No settings conflicts recorded.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (sub === 'resolve') {
|
|
63
|
+
const key = args[1] as ConfigKey | undefined;
|
|
64
|
+
const resolution = (args[2] ?? '').toLowerCase();
|
|
65
|
+
if (!key || !CONFIG_KEYS.has(key) || (resolution !== 'local' && resolution !== 'synced')) {
|
|
66
|
+
ctx.print('Usage: /settingssync resolve <config-key> <local|synced>');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const changed = resolveSettingsSyncConflict(ctx.platform.configManager, key, resolution);
|
|
70
|
+
if (!changed) {
|
|
71
|
+
ctx.print(`No synced conflict found for ${key}.`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
ctx.session.runtime.model = String(ctx.platform.configManager.get('provider.model'));
|
|
75
|
+
ctx.session.runtime.provider = getProviderIdFromModel(ctx.platform.configManager.get('provider.model'));
|
|
76
|
+
ctx.session.runtime.reasoningEffort = ctx.platform.configManager.get('provider.reasoningEffort') as string;
|
|
77
|
+
ctx.print(`Resolved synced conflict for ${key} using the ${resolution} value.`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (sub === 'failures') {
|
|
81
|
+
const snapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
82
|
+
ctx.print(snapshot.recentFailures.length > 0
|
|
83
|
+
? [
|
|
84
|
+
'Settings Sync Failures',
|
|
85
|
+
...snapshot.recentFailures.map((failure) => ` ${failure.surface} ${failure.message}`),
|
|
86
|
+
].join('\n')
|
|
87
|
+
: 'Settings Sync Failures\n No recent sync or managed-setting failures recorded.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (sub === 'rollback-history') {
|
|
91
|
+
const snapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
92
|
+
ctx.print(snapshot.rollbackHistory.length > 0
|
|
93
|
+
? [
|
|
94
|
+
'Managed Rollback History',
|
|
95
|
+
...snapshot.rollbackHistory.map((entry) => (
|
|
96
|
+
` ${entry.token} ${entry.profileName} restored=${entry.restoredKeys.length} ${new Date(entry.appliedAt).toLocaleString()}`
|
|
97
|
+
)),
|
|
98
|
+
].join('\n')
|
|
99
|
+
: 'Managed Rollback History\n No managed apply rollback records yet.');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (sub === 'export' || sub === 'push') {
|
|
103
|
+
const pathArg = args[1];
|
|
104
|
+
if (!pathArg) {
|
|
105
|
+
ctx.print(`Usage: /settingssync ${sub} <path>`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
109
|
+
const bundle = exportSettingsSyncBundle(ctx.platform.configManager);
|
|
110
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
111
|
+
writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
|
|
112
|
+
recordSettingsSyncEvent({
|
|
113
|
+
surface: 'settings-sync',
|
|
114
|
+
direction: sub === 'push' ? 'push' : 'export',
|
|
115
|
+
path: targetPath,
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
detail: `${Object.keys(bundle.settings).length} settings exported`,
|
|
118
|
+
}, controlPlaneConfigDir);
|
|
119
|
+
ctx.print(`Settings sync bundle exported to ${targetPath}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (sub === 'inspect') {
|
|
123
|
+
const pathArg = args[1];
|
|
124
|
+
if (!pathArg) {
|
|
125
|
+
ctx.print('Usage: /settingssync inspect <path>');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
129
|
+
const bundle = JSON.parse(readFileSync(sourcePath, 'utf-8')) as SettingsSyncBundle;
|
|
130
|
+
ctx.print(inspectSettingsSyncBundle(bundle));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (sub === 'pull') {
|
|
134
|
+
const pathArg = args[1];
|
|
135
|
+
if (!pathArg) {
|
|
136
|
+
ctx.print('Usage: /settingssync pull <path>');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
140
|
+
try {
|
|
141
|
+
const bundle = JSON.parse(readFileSync(sourcePath, 'utf-8')) as SettingsSyncBundle;
|
|
142
|
+
const result = applySettingsSyncBundle(ctx.platform.configManager, bundle, sourcePath);
|
|
143
|
+
ctx.print(`Settings sync bundle pulled from ${sourcePath} (${result.appliedCount} applied, ${result.conflictCount} conflicts).`);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
recordSettingsSyncFailure('settings-sync', summarizeError(error), controlPlaneConfigDir);
|
|
146
|
+
ctx.print(summarizeError(error));
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (sub === 'lock') {
|
|
151
|
+
const key = args[1] as ConfigKey | undefined;
|
|
152
|
+
const source = args[2];
|
|
153
|
+
const reason = args.slice(3).join(' ').trim();
|
|
154
|
+
if (!key || !source || !reason || !CONFIG_KEYS.has(key)) {
|
|
155
|
+
ctx.print('Usage: /settingssync lock <config-key> <source> <reason...>');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
setManagedSettingLock(key, source, reason, controlPlaneConfigDir);
|
|
159
|
+
ctx.print(`Managed lock recorded for ${key}.`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (sub === 'unlock') {
|
|
163
|
+
const key = args[1] as ConfigKey | undefined;
|
|
164
|
+
if (!key || !CONFIG_KEYS.has(key)) {
|
|
165
|
+
ctx.print('Usage: /settingssync unlock <config-key>');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
ctx.print(clearManagedSettingLock(key, controlPlaneConfigDir) ? `Managed lock cleared for ${key}.` : `No managed lock found for ${key}.`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
ctx.print(formatSettingsControlPlaneReview(ctx.platform.configManager).join('\n'));
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
|
+
import {
|
|
5
|
+
type ExportMessage,
|
|
6
|
+
defaultExportPath,
|
|
7
|
+
exportToHTML,
|
|
8
|
+
exportToJSON,
|
|
9
|
+
exportToMarkdownExtended,
|
|
10
|
+
} from '@pellux/goodvibes-sdk/platform/export';
|
|
11
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
12
|
+
import { requireShellPaths } from './runtime-services.ts';
|
|
13
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
14
|
+
|
|
15
|
+
export function registerShareRuntimeCommands(registry: CommandRegistry): void {
|
|
16
|
+
registry.register({
|
|
17
|
+
name: 'share',
|
|
18
|
+
aliases: [],
|
|
19
|
+
description: 'Export the current session to a shareable format (html, json, md)',
|
|
20
|
+
usage: '<html|json|md> [path] [--redact]',
|
|
21
|
+
argsHint: '<html|json|md> [path]',
|
|
22
|
+
async handler(args, ctx) {
|
|
23
|
+
const shellPaths = requireShellPaths(ctx);
|
|
24
|
+
const FORMATS = ['html', 'json', 'md'] as const;
|
|
25
|
+
type Format = typeof FORMATS[number];
|
|
26
|
+
|
|
27
|
+
const format = args[0]?.toLowerCase() as Format | undefined;
|
|
28
|
+
if (!format || !FORMATS.includes(format)) {
|
|
29
|
+
ctx.print(
|
|
30
|
+
'Usage: /share <html|json|md> [path] [--redact]\n'
|
|
31
|
+
+ ' html — self-contained HTML with syntax highlighting\n'
|
|
32
|
+
+ ' json — structured JSON (machine-readable)\n'
|
|
33
|
+
+ ' md — Markdown\n\n'
|
|
34
|
+
+ 'Options:\n'
|
|
35
|
+
+ ' --redact Redact API keys and personal paths from output\n\n'
|
|
36
|
+
+ 'Default path: ~/goodvibes-exports/session-<timestamp>.<ext>',
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const remainingArgs = args.slice(1);
|
|
42
|
+
const redact = remainingArgs.includes('--redact');
|
|
43
|
+
const pathArgs = remainingArgs.filter(a => a !== '--redact');
|
|
44
|
+
const outputPath = pathArgs.length > 0
|
|
45
|
+
? shellPaths.resolveWorkspacePath(pathArgs[0])
|
|
46
|
+
: defaultExportPath(format, shellPaths.homeDirectory);
|
|
47
|
+
|
|
48
|
+
const convData = ctx.session.conversationManager.toJSON() as {
|
|
49
|
+
messages: Array<{
|
|
50
|
+
role: string;
|
|
51
|
+
content: unknown;
|
|
52
|
+
toolCalls?: Array<{ id: string; name: string; arguments: Record<string, unknown> }>;
|
|
53
|
+
callId?: string;
|
|
54
|
+
toolName?: string;
|
|
55
|
+
reasoningContent?: string;
|
|
56
|
+
reasoningSummary?: string;
|
|
57
|
+
usage?: {
|
|
58
|
+
inputTokens: number;
|
|
59
|
+
outputTokens: number;
|
|
60
|
+
cacheReadTokens?: number;
|
|
61
|
+
cacheWriteTokens?: number;
|
|
62
|
+
};
|
|
63
|
+
cancelled?: boolean;
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (!convData.messages || convData.messages.length === 0) {
|
|
68
|
+
ctx.print('Nothing to export — conversation is empty.');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const messages: ExportMessage[] = convData.messages.map(m => ({
|
|
73
|
+
role: m.role as ExportMessage['role'],
|
|
74
|
+
content: m.content as string,
|
|
75
|
+
toolCalls: m.toolCalls,
|
|
76
|
+
callId: m.callId,
|
|
77
|
+
toolName: m.toolName,
|
|
78
|
+
reasoningContent: m.reasoningContent,
|
|
79
|
+
reasoningSummary: m.reasoningSummary,
|
|
80
|
+
usage: m.usage,
|
|
81
|
+
cancelled: m.cancelled,
|
|
82
|
+
}));
|
|
83
|
+
const metadata = {
|
|
84
|
+
model: ctx.session.runtime.model,
|
|
85
|
+
provider: ctx.session.runtime.provider,
|
|
86
|
+
sessionId: ctx.session.runtime.sessionId,
|
|
87
|
+
title: ctx.session.conversationManager.title || undefined,
|
|
88
|
+
};
|
|
89
|
+
const options = { redact };
|
|
90
|
+
|
|
91
|
+
let outputContent: string;
|
|
92
|
+
try {
|
|
93
|
+
if (format === 'html') outputContent = exportToHTML(messages, metadata, options);
|
|
94
|
+
else if (format === 'json') outputContent = exportToJSON(messages, metadata, options);
|
|
95
|
+
else outputContent = exportToMarkdownExtended(messages, metadata, options);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
ctx.print(`Export failed: ${summarizeError(err)}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { mkdirSync } = await import('node:fs');
|
|
102
|
+
const { dirname } = await import('node:path');
|
|
103
|
+
try {
|
|
104
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
105
|
+
} catch (mkdirErr) {
|
|
106
|
+
logger.warn(`[share] mkdir failed for ${dirname(outputPath)}:`, mkdirErr instanceof Error ? { message: mkdirErr.message } : undefined);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await writeFile(outputPath, outputContent, 'utf-8');
|
|
111
|
+
} catch (err) {
|
|
112
|
+
ctx.print(`Failed to write export: ${summarizeError(err)}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
ctx.print(`Exported ${format.toUpperCase()} session to ${outputPath}${redact ? ' (sensitive data redacted)' : ''}`);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|