@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,711 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ProjectPlanningDecision,
|
|
3
|
+
ProjectPlanningEvaluation,
|
|
4
|
+
ProjectPlanningLanguageArtifact,
|
|
5
|
+
ProjectPlanningQuestion,
|
|
6
|
+
ProjectPlanningService,
|
|
7
|
+
ProjectPlanningState,
|
|
8
|
+
ProjectPlanningStatus,
|
|
9
|
+
} from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
10
|
+
import type { Line } from '../types/grid.ts';
|
|
11
|
+
import { BasePanel } from './base-panel.ts';
|
|
12
|
+
import {
|
|
13
|
+
buildBodyText,
|
|
14
|
+
buildEmptyState,
|
|
15
|
+
buildKeyValueLine,
|
|
16
|
+
buildPanelLine,
|
|
17
|
+
buildPanelListRow,
|
|
18
|
+
buildPanelWorkspace,
|
|
19
|
+
DEFAULT_PANEL_PALETTE,
|
|
20
|
+
extendPalette,
|
|
21
|
+
resolveScrollablePanelSection,
|
|
22
|
+
type PanelWorkspaceSection,
|
|
23
|
+
} from './polish.ts';
|
|
24
|
+
|
|
25
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
26
|
+
planning: '#38bdf8',
|
|
27
|
+
blocked: '#f97316',
|
|
28
|
+
approved: '#22c55e',
|
|
29
|
+
rejected: '#ef4444',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
interface ProjectPlanningPanelSnapshot {
|
|
33
|
+
readonly status: ProjectPlanningStatus | null;
|
|
34
|
+
readonly state: ProjectPlanningState | null;
|
|
35
|
+
readonly evaluation: ProjectPlanningEvaluation | null;
|
|
36
|
+
readonly decisions: readonly ProjectPlanningDecision[];
|
|
37
|
+
readonly language: ProjectPlanningLanguageArtifact | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ProjectPlanningPanelOptions {
|
|
41
|
+
readonly service: ProjectPlanningService;
|
|
42
|
+
readonly projectId: string;
|
|
43
|
+
readonly requestRender?: () => void;
|
|
44
|
+
readonly submitAnswer?: (answer: string) => void;
|
|
45
|
+
readonly dismissPlanning?: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface PlanningAnswerAction {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly label: string;
|
|
51
|
+
readonly detail: string;
|
|
52
|
+
readonly answer: string;
|
|
53
|
+
readonly kind?: 'answer' | 'approve' | 'dismiss';
|
|
54
|
+
readonly disabled?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface RenderedPlanningSection extends PanelWorkspaceSection {
|
|
58
|
+
readonly selectedLineIndex?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class ProjectPlanningPanel extends BasePanel {
|
|
62
|
+
private readonly service: ProjectPlanningService;
|
|
63
|
+
private readonly projectId: string;
|
|
64
|
+
private readonly requestRender: () => void;
|
|
65
|
+
private readonly submitAnswer: ((answer: string) => void) | undefined;
|
|
66
|
+
private readonly dismissPlanning: (() => void) | undefined;
|
|
67
|
+
private snapshot: ProjectPlanningPanelSnapshot | null = null;
|
|
68
|
+
private loading = false;
|
|
69
|
+
private scrollOffset = 0;
|
|
70
|
+
private selectedActionIndex = 0;
|
|
71
|
+
private draftAnswer = '';
|
|
72
|
+
|
|
73
|
+
public constructor(options: ProjectPlanningPanelOptions) {
|
|
74
|
+
super('project-planning', 'Planning', 'P', 'agent');
|
|
75
|
+
this.service = options.service;
|
|
76
|
+
this.projectId = options.projectId;
|
|
77
|
+
this.requestRender = options.requestRender ?? (() => {});
|
|
78
|
+
this.submitAnswer = options.submitAnswer;
|
|
79
|
+
this.dismissPlanning = options.dismissPlanning;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public override onActivate(): void {
|
|
83
|
+
super.onActivate();
|
|
84
|
+
this.refresh();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public handleInput(key: string): boolean {
|
|
88
|
+
if (this.lastError !== null) this.clearError();
|
|
89
|
+
const question = this.getCurrentQuestion();
|
|
90
|
+
if (question) {
|
|
91
|
+
const actions = this.getAnswerActions(question);
|
|
92
|
+
this.selectedActionIndex = this.clampActionIndex(actions.length);
|
|
93
|
+
if (key === 'up') {
|
|
94
|
+
this.selectedActionIndex = Math.max(0, this.selectedActionIndex - 1);
|
|
95
|
+
this.markDirty();
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (key === 'down') {
|
|
99
|
+
this.selectedActionIndex = Math.min(Math.max(0, actions.length - 1), this.selectedActionIndex + 1);
|
|
100
|
+
this.markDirty();
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (key === 'enter' || key === 'return') {
|
|
104
|
+
this.submitSelectedAction(question, actions);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
if (key === 'backspace') {
|
|
108
|
+
this.draftAnswer = this.draftAnswer.slice(0, -1);
|
|
109
|
+
this.markDirty();
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (key === 'delete') {
|
|
113
|
+
this.draftAnswer = '';
|
|
114
|
+
this.markDirty();
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (key === 'space') {
|
|
118
|
+
this.draftAnswer += ' ';
|
|
119
|
+
this.markDirty();
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (key === 'pageup') {
|
|
123
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 6);
|
|
124
|
+
this.markDirty();
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (key === 'pagedown') {
|
|
128
|
+
this.scrollOffset += 6;
|
|
129
|
+
this.markDirty();
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (this.isPrintableKey(key)) {
|
|
133
|
+
this.draftAnswer += key;
|
|
134
|
+
this.markDirty();
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (key === 'r' || key === 'R') {
|
|
141
|
+
this.refresh(true);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
if (key === 'a' || key === 'A') {
|
|
145
|
+
this.approveExecution();
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
if (key === 'up' || key === 'k') {
|
|
149
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
150
|
+
this.markDirty();
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (key === 'down' || key === 'j') {
|
|
154
|
+
this.scrollOffset += 1;
|
|
155
|
+
this.markDirty();
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public render(width: number, height: number): Line[] {
|
|
162
|
+
return this.trackedRender(() => {
|
|
163
|
+
if (!this.snapshot && !this.loading) this.refresh();
|
|
164
|
+
|
|
165
|
+
const sections: RenderedPlanningSection[] = [];
|
|
166
|
+
const status = this.snapshot?.status;
|
|
167
|
+
const state = this.snapshot?.state;
|
|
168
|
+
const evaluation = this.snapshot?.evaluation ?? null;
|
|
169
|
+
const language = this.snapshot?.language ?? null;
|
|
170
|
+
const decisions = this.snapshot?.decisions ?? [];
|
|
171
|
+
|
|
172
|
+
sections.push({
|
|
173
|
+
title: 'Workspace',
|
|
174
|
+
lines: [
|
|
175
|
+
buildKeyValueLine(width, [
|
|
176
|
+
{ label: 'project', value: this.projectId, valueColor: C.planning },
|
|
177
|
+
{ label: 'space', value: status?.knowledgeSpaceId ?? `project:${this.projectId}`, valueColor: C.value },
|
|
178
|
+
{ label: 'mode', value: 'TUI-owned passive backing store', valueColor: C.info },
|
|
179
|
+
], C),
|
|
180
|
+
buildPanelLine(width, [
|
|
181
|
+
[' Planning never starts from daemon, webhooks, ntfy, Home Assistant, or companion surfaces.', C.dim],
|
|
182
|
+
]),
|
|
183
|
+
],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (!state) {
|
|
187
|
+
sections.push({
|
|
188
|
+
title: 'No Active Planning State',
|
|
189
|
+
lines: buildEmptyState(
|
|
190
|
+
width,
|
|
191
|
+
'No project planning state has been saved for this workspace.',
|
|
192
|
+
'Describe the intended change in normal chat to let the TUI start the planning interview. The SDK only stores and evaluates artifacts.',
|
|
193
|
+
[],
|
|
194
|
+
C,
|
|
195
|
+
),
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
sections.push(this.buildStateSection(width, state, evaluation));
|
|
199
|
+
const question = this.getCurrentQuestion();
|
|
200
|
+
if (question) sections.push(this.buildQuestionSection(width, question));
|
|
201
|
+
sections.push(this.buildGapsSection(width, evaluation));
|
|
202
|
+
sections.push(this.buildTasksSection(width, state));
|
|
203
|
+
sections.push(this.buildDecisionsSection(width, state, decisions));
|
|
204
|
+
sections.push(this.buildLanguageSection(width, language));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (this.lastError) {
|
|
208
|
+
const line = this.renderErrorLine(width);
|
|
209
|
+
if (line) sections.push({ title: 'Error', lines: [line] });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const { lines: flattened, selectedIndex } = this.flattenSections(width, sections);
|
|
213
|
+
const scroll = resolveScrollablePanelSection(width, height, {
|
|
214
|
+
intro: 'Project planning state, readiness gaps, decisions, language, task graph, verification gates, and agent handoff metadata.',
|
|
215
|
+
footerLines: this.footerLines(width),
|
|
216
|
+
palette: C,
|
|
217
|
+
section: {
|
|
218
|
+
title: 'Project Planning',
|
|
219
|
+
scrollableLines: flattened,
|
|
220
|
+
selectedIndex,
|
|
221
|
+
scrollOffset: this.scrollOffset,
|
|
222
|
+
appendWindowSummary: {},
|
|
223
|
+
minRows: 8,
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
this.scrollOffset = scroll.scrollOffset;
|
|
227
|
+
|
|
228
|
+
return buildPanelWorkspace(width, height, {
|
|
229
|
+
title: this.loading ? 'Project Planning - loading' : 'Project Planning',
|
|
230
|
+
intro: 'Passive SDK-backed planning artifacts for the current workspace. Conversation control stays inside this TUI.',
|
|
231
|
+
sections: [scroll.section],
|
|
232
|
+
footerLines: this.footerLines(width),
|
|
233
|
+
palette: C,
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private footerLines(width: number): Line[] {
|
|
239
|
+
const hasQuestion = this.getCurrentQuestion() !== null;
|
|
240
|
+
if (hasQuestion) {
|
|
241
|
+
return [
|
|
242
|
+
buildPanelLine(width, [
|
|
243
|
+
[' Up/Down', C.info],
|
|
244
|
+
[' choose answer ', C.dim],
|
|
245
|
+
['type', C.info],
|
|
246
|
+
[' draft ', C.dim],
|
|
247
|
+
['Backspace/Delete', C.info],
|
|
248
|
+
[' edit ', C.dim],
|
|
249
|
+
['Enter', C.info],
|
|
250
|
+
[' submit Esc prompt focus Ctrl+X close panel', C.dim],
|
|
251
|
+
]),
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
return [
|
|
255
|
+
buildPanelLine(width, [
|
|
256
|
+
[' Up/Down', C.info],
|
|
257
|
+
[' scroll ', C.dim],
|
|
258
|
+
['r', C.info],
|
|
259
|
+
[' refresh ', C.dim],
|
|
260
|
+
['a', C.info],
|
|
261
|
+
[' approve execution-ready plan Esc prompt focus Ctrl+X close panel', C.dim],
|
|
262
|
+
]),
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private flattenSections(
|
|
267
|
+
width: number,
|
|
268
|
+
sections: readonly RenderedPlanningSection[],
|
|
269
|
+
): { readonly lines: Line[]; readonly selectedIndex: number } {
|
|
270
|
+
const lines: Line[] = [];
|
|
271
|
+
let selectedIndex = 0;
|
|
272
|
+
for (const section of sections) {
|
|
273
|
+
const sectionStart = lines.length;
|
|
274
|
+
const titleOffset = section.title ? 1 : 0;
|
|
275
|
+
if (section.title) lines.push(buildPanelLine(width, [[` ${section.title}`, C.label]]));
|
|
276
|
+
lines.push(...section.lines);
|
|
277
|
+
if (section.selectedLineIndex !== undefined) {
|
|
278
|
+
selectedIndex = sectionStart + titleOffset + section.selectedLineIndex;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return { lines, selectedIndex };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private buildQuestionSection(width: number, question: ProjectPlanningQuestion): RenderedPlanningSection {
|
|
285
|
+
const actions = this.getAnswerActions(question);
|
|
286
|
+
this.selectedActionIndex = this.clampActionIndex(actions.length);
|
|
287
|
+
const lines: Line[] = [
|
|
288
|
+
...buildBodyText(width, question.prompt, C, C.planning),
|
|
289
|
+
];
|
|
290
|
+
if (question.whyItMatters) {
|
|
291
|
+
lines.push(...buildBodyText(width, `Why this matters: ${question.whyItMatters}`, C, C.dim));
|
|
292
|
+
}
|
|
293
|
+
if (question.recommendedAnswer && !this.isGenericRecommendation(question.recommendedAnswer)) {
|
|
294
|
+
lines.push(...buildBodyText(width, `Recommendation: ${question.recommendedAnswer}`, C, C.good));
|
|
295
|
+
}
|
|
296
|
+
lines.push(...buildBodyText(
|
|
297
|
+
width,
|
|
298
|
+
`Typed answer: ${this.draftAnswer || '(type here while this panel is focused)'}`,
|
|
299
|
+
C,
|
|
300
|
+
this.draftAnswer ? C.value : C.dim,
|
|
301
|
+
));
|
|
302
|
+
lines.push(buildPanelLine(width, [[
|
|
303
|
+
' Select an answer below or type your own. Enter sends it through the normal planning chat path.',
|
|
304
|
+
C.dim,
|
|
305
|
+
]]));
|
|
306
|
+
const selectedLineIndex = lines.length + this.selectedActionIndex;
|
|
307
|
+
actions.forEach((action, index) => {
|
|
308
|
+
const selected = index === this.selectedActionIndex;
|
|
309
|
+
lines.push(buildPanelListRow(width, [
|
|
310
|
+
{ text: action.label, fg: action.disabled ? C.dim : C.value, bold: selected },
|
|
311
|
+
{ text: ` ${action.detail}`, fg: C.dim },
|
|
312
|
+
], C, {
|
|
313
|
+
selected,
|
|
314
|
+
marker: selected ? '▶' : ' ',
|
|
315
|
+
}));
|
|
316
|
+
});
|
|
317
|
+
return { title: 'Answer Current Question', lines, selectedLineIndex };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private buildStateSection(
|
|
321
|
+
width: number,
|
|
322
|
+
state: ProjectPlanningState,
|
|
323
|
+
evaluation: ProjectPlanningEvaluation | null,
|
|
324
|
+
): PanelWorkspaceSection {
|
|
325
|
+
const readiness = evaluation?.readiness ?? state.readiness;
|
|
326
|
+
const readinessColor = readiness === 'executable'
|
|
327
|
+
? C.approved
|
|
328
|
+
: readiness === 'needs-user-input'
|
|
329
|
+
? C.blocked
|
|
330
|
+
: C.dim;
|
|
331
|
+
const lines: Line[] = [
|
|
332
|
+
buildKeyValueLine(width, [
|
|
333
|
+
{ label: 'readiness', value: readiness, valueColor: readinessColor },
|
|
334
|
+
{ label: 'approved', value: state.executionApproved ? 'yes' : 'no', valueColor: state.executionApproved ? C.approved : C.blocked },
|
|
335
|
+
{ label: 'questions', value: `${state.openQuestions.length} open / ${state.answeredQuestions.length} answered`, valueColor: C.value },
|
|
336
|
+
], C),
|
|
337
|
+
...buildBodyText(width, `Goal: ${state.goal || '(not set)'}`, C, state.goal ? C.value : C.blocked),
|
|
338
|
+
];
|
|
339
|
+
if (state.scope) lines.push(...buildBodyText(width, `Scope: ${state.scope}`, C, C.value));
|
|
340
|
+
if (state.knownContext.length) {
|
|
341
|
+
lines.push(...buildBodyText(width, `Known context: ${state.knownContext.join(' | ')}`, C, C.dim));
|
|
342
|
+
}
|
|
343
|
+
if (evaluation?.nextQuestion) {
|
|
344
|
+
lines.push(...buildBodyText(width, `Next question: ${evaluation.nextQuestion.prompt}`, C, C.planning));
|
|
345
|
+
if (evaluation.nextQuestion.whyItMatters) {
|
|
346
|
+
lines.push(...buildBodyText(width, `Why it matters: ${evaluation.nextQuestion.whyItMatters}`, C, C.dim));
|
|
347
|
+
}
|
|
348
|
+
if (evaluation.nextQuestion.recommendedAnswer && !this.isGenericRecommendation(evaluation.nextQuestion.recommendedAnswer)) {
|
|
349
|
+
lines.push(...buildBodyText(width, `Recommended answer: ${evaluation.nextQuestion.recommendedAnswer}`, C, C.good));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return { title: 'State', lines };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private buildGapsSection(width: number, evaluation: ProjectPlanningEvaluation | null): PanelWorkspaceSection {
|
|
356
|
+
const gaps = evaluation?.gaps ?? [];
|
|
357
|
+
if (gaps.length === 0) {
|
|
358
|
+
return {
|
|
359
|
+
title: 'Readiness Gaps',
|
|
360
|
+
lines: [buildPanelLine(width, [[' No readiness gaps.', C.good]])],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
title: 'Readiness Gaps',
|
|
365
|
+
lines: gaps.slice(0, 12).flatMap((gap) => buildBodyText(
|
|
366
|
+
width,
|
|
367
|
+
`${gap.severity.toUpperCase()} ${gap.kind}: ${gap.message}`,
|
|
368
|
+
C,
|
|
369
|
+
gap.severity === 'blocking' ? C.blocked : C.warn,
|
|
370
|
+
)),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private buildTasksSection(width: number, state: ProjectPlanningState): PanelWorkspaceSection {
|
|
375
|
+
const lines: Line[] = [];
|
|
376
|
+
if (state.tasks.length === 0) {
|
|
377
|
+
lines.push(buildPanelLine(width, [[' No decomposed tasks recorded yet.', C.dim]]));
|
|
378
|
+
} else {
|
|
379
|
+
for (const task of state.tasks) {
|
|
380
|
+
lines.push(...buildBodyText(
|
|
381
|
+
width,
|
|
382
|
+
`${task.id}: ${task.title} [${task.status ?? 'pending'}]${task.canRunConcurrently ? ' - concurrent' : ''}`,
|
|
383
|
+
C,
|
|
384
|
+
task.blockedOnUserInput ? C.blocked : C.value,
|
|
385
|
+
));
|
|
386
|
+
if (task.dependencies?.length) lines.push(...buildBodyText(width, `Dependencies: ${task.dependencies.join(', ')}`, C, C.dim));
|
|
387
|
+
if (task.verification?.length) lines.push(...buildBodyText(width, `Verification: ${task.verification.join(' | ')}`, C, C.good));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (state.verificationGates.length) {
|
|
392
|
+
lines.push(buildPanelLine(width, [[' Verification gates:', C.label]]));
|
|
393
|
+
for (const gate of state.verificationGates) {
|
|
394
|
+
lines.push(...buildBodyText(width, `${gate.id}: ${gate.description} [${gate.status ?? 'pending'}]`, C, gate.required === false ? C.dim : C.good));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (state.agentAssignments.length) {
|
|
398
|
+
lines.push(buildPanelLine(width, [[' Agent handoff candidates:', C.label]]));
|
|
399
|
+
for (const assignment of state.agentAssignments) {
|
|
400
|
+
lines.push(...buildBodyText(
|
|
401
|
+
width,
|
|
402
|
+
`${assignment.taskId}: ${assignment.agentType ?? 'none'}${assignment.canRunConcurrently ? ' - can run concurrently' : ''}`,
|
|
403
|
+
C,
|
|
404
|
+
C.info,
|
|
405
|
+
));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return { title: 'Task Graph', lines };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private buildDecisionsSection(
|
|
412
|
+
width: number,
|
|
413
|
+
state: ProjectPlanningState,
|
|
414
|
+
storedDecisions: readonly ProjectPlanningDecision[],
|
|
415
|
+
): PanelWorkspaceSection {
|
|
416
|
+
const byId = new Map<string, ProjectPlanningDecision>();
|
|
417
|
+
for (const decision of [...storedDecisions, ...state.decisions]) byId.set(decision.id, decision);
|
|
418
|
+
const decisions = [...byId.values()];
|
|
419
|
+
if (decisions.length === 0) {
|
|
420
|
+
return {
|
|
421
|
+
title: 'Decisions',
|
|
422
|
+
lines: [buildPanelLine(width, [[' No durable planning decisions recorded yet.', C.dim]])],
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
title: 'Decisions',
|
|
427
|
+
lines: decisions.slice(0, 12).flatMap((decision) => buildBodyText(
|
|
428
|
+
width,
|
|
429
|
+
`${decision.title}: ${decision.decision} [${decision.status ?? 'accepted'}]`,
|
|
430
|
+
C,
|
|
431
|
+
decision.status === 'rejected' ? C.rejected : C.value,
|
|
432
|
+
)),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private buildLanguageSection(width: number, language: ProjectPlanningLanguageArtifact | null): PanelWorkspaceSection {
|
|
437
|
+
if (!language || (language.terms.length === 0 && language.ambiguities.length === 0)) {
|
|
438
|
+
return {
|
|
439
|
+
title: 'Project Language',
|
|
440
|
+
lines: [buildPanelLine(width, [[' No project language terms or ambiguity resolutions recorded yet.', C.dim]])],
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
const lines: Line[] = [];
|
|
444
|
+
for (const term of language.terms.slice(0, 8)) {
|
|
445
|
+
lines.push(...buildBodyText(width, `${term.term}: ${term.definition}`, C, C.value));
|
|
446
|
+
if (term.avoid?.length) lines.push(...buildBodyText(width, `Avoid: ${term.avoid.join(', ')}`, C, C.blocked));
|
|
447
|
+
}
|
|
448
|
+
for (const ambiguity of language.ambiguities.slice(0, 8)) {
|
|
449
|
+
lines.push(...buildBodyText(width, `Resolved ambiguity - ${ambiguity.phrase}: ${ambiguity.resolution}`, C, C.info));
|
|
450
|
+
}
|
|
451
|
+
return { title: 'Project Language', lines };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private refresh(force = false): void {
|
|
455
|
+
if (this.loading && !force) return;
|
|
456
|
+
this.loading = true;
|
|
457
|
+
this.markDirty();
|
|
458
|
+
this.requestRender();
|
|
459
|
+
void (async () => {
|
|
460
|
+
try {
|
|
461
|
+
const [status, stateResult, decisionsResult, languageResult] = await Promise.all([
|
|
462
|
+
this.service.status({ projectId: this.projectId }),
|
|
463
|
+
this.service.getState({ projectId: this.projectId }),
|
|
464
|
+
this.service.listDecisions({ projectId: this.projectId }),
|
|
465
|
+
this.service.getLanguage({ projectId: this.projectId }),
|
|
466
|
+
]);
|
|
467
|
+
const evaluation = await this.service.evaluate({ projectId: this.projectId });
|
|
468
|
+
this.snapshot = {
|
|
469
|
+
status,
|
|
470
|
+
state: stateResult.state,
|
|
471
|
+
evaluation,
|
|
472
|
+
decisions: decisionsResult.decisions,
|
|
473
|
+
language: languageResult.language,
|
|
474
|
+
};
|
|
475
|
+
this.clearError();
|
|
476
|
+
} catch (err) {
|
|
477
|
+
this.setError(err instanceof Error ? err.message : String(err));
|
|
478
|
+
} finally {
|
|
479
|
+
this.loading = false;
|
|
480
|
+
this.markDirty();
|
|
481
|
+
this.requestRender();
|
|
482
|
+
}
|
|
483
|
+
})();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private getCurrentQuestion(): ProjectPlanningQuestion | null {
|
|
487
|
+
const state = this.snapshot?.state;
|
|
488
|
+
const open = state?.openQuestions.find((question) => (question.status ?? 'open') === 'open');
|
|
489
|
+
return open ?? this.snapshot?.evaluation?.nextQuestion ?? null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private getAnswerActions(question: ProjectPlanningQuestion): PlanningAnswerAction[] {
|
|
493
|
+
const actions: PlanningAnswerAction[] = [];
|
|
494
|
+
const prompt = question.prompt.toLowerCase();
|
|
495
|
+
const isScopeQuestion = prompt.includes('scope') || prompt.includes('in or out');
|
|
496
|
+
const isTaskQuestion = prompt.includes('task') || prompt.includes('dependency') || prompt.includes('work breakdown');
|
|
497
|
+
const isVerificationQuestion = prompt.includes('verification') || prompt.includes('test') || prompt.includes('prove');
|
|
498
|
+
const isApprovalQuestion = prompt.includes('approved') || prompt.includes('approve') || prompt.includes('execution');
|
|
499
|
+
if (isApprovalQuestion) {
|
|
500
|
+
actions.push({
|
|
501
|
+
id: 'approve-execution',
|
|
502
|
+
label: 'Approve execution',
|
|
503
|
+
detail: 'Mark this plan approved so execution may proceed.',
|
|
504
|
+
answer: 'Approve this planning state for execution.',
|
|
505
|
+
kind: 'approve',
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
if (isScopeQuestion) {
|
|
509
|
+
actions.push({
|
|
510
|
+
id: 'scope-focused-first-pass',
|
|
511
|
+
label: 'Use focused first-pass scope',
|
|
512
|
+
detail: 'Fill a concrete end-to-end scope for this goal and keep unrelated work out.',
|
|
513
|
+
answer: 'Use a focused first-pass scope for this goal.',
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
if (isTaskQuestion) {
|
|
517
|
+
actions.push({
|
|
518
|
+
id: 'tasks-default-breakdown',
|
|
519
|
+
label: 'Create default task breakdown',
|
|
520
|
+
detail: 'Create inspect, implement, wire, and verify tasks with dependencies.',
|
|
521
|
+
answer: 'Create the default task breakdown for this goal.',
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
if (isVerificationQuestion) {
|
|
525
|
+
actions.push({
|
|
526
|
+
id: 'verification-default-gates',
|
|
527
|
+
label: 'Use standard verification gates',
|
|
528
|
+
detail: 'Require focused regression coverage, typecheck/build validation, and a runtime smoke where feasible.',
|
|
529
|
+
answer: 'Use standard verification gates for this goal.',
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
if (question.recommendedAnswer?.trim() && !this.isGenericRecommendation(question.recommendedAnswer)) {
|
|
533
|
+
actions.push({
|
|
534
|
+
id: 'recommended',
|
|
535
|
+
label: 'Use recommended answer',
|
|
536
|
+
detail: this.compact(question.recommendedAnswer),
|
|
537
|
+
answer: question.recommendedAnswer,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
if (isScopeQuestion) {
|
|
541
|
+
actions.push({
|
|
542
|
+
id: 'scope-end-to-end',
|
|
543
|
+
label: 'End-to-end required scope',
|
|
544
|
+
detail: 'Let the plan include every component needed to make this work, but avoid unrelated cleanup.',
|
|
545
|
+
answer: 'Scope is everything required to make the requested outcome work end-to-end. Include TUI, daemon composition, configuration, docs, and tests if they are required. Do not include unrelated cleanup or broad refactors unless they are necessary for this task.',
|
|
546
|
+
});
|
|
547
|
+
actions.push({
|
|
548
|
+
id: 'scope-agent-first',
|
|
549
|
+
label: 'Agent-first scope',
|
|
550
|
+
detail: 'Fix Agent behavior here; report SDK blockers instead of patching around SDK-owned bugs.',
|
|
551
|
+
answer: 'Scope is Agent-owned behavior first. If a blocker is SDK-owned, report the exact SDK contract/runtime issue instead of patching around it locally. Include daemon contracts only where Agent consumes public daemon routes.',
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
actions.push({
|
|
555
|
+
id: 'ask-narrower',
|
|
556
|
+
label: 'I am not sure yet',
|
|
557
|
+
detail: 'Break this into smaller concrete choices with examples and a recommended default.',
|
|
558
|
+
answer: `I do not know enough to answer "${question.prompt}" as asked. Break it into smaller concrete questions with 2-4 specific choices, explain the tradeoffs, recommend a default, and ask me the first one.`,
|
|
559
|
+
});
|
|
560
|
+
actions.push({
|
|
561
|
+
id: 'custom',
|
|
562
|
+
label: 'Submit typed answer',
|
|
563
|
+
detail: this.draftAnswer ? this.compact(this.draftAnswer) : 'Type an answer first; this row becomes the custom answer.',
|
|
564
|
+
answer: this.draftAnswer.trim(),
|
|
565
|
+
disabled: !this.draftAnswer.trim(),
|
|
566
|
+
});
|
|
567
|
+
actions.push({
|
|
568
|
+
id: 'dismiss-planning',
|
|
569
|
+
label: 'Close planning and continue without it',
|
|
570
|
+
detail: 'Pause project planning for this workspace. Normal chat continues; /plan can reopen it later.',
|
|
571
|
+
answer: 'Pause project planning for this workspace and continue without the planning panel.',
|
|
572
|
+
kind: 'dismiss',
|
|
573
|
+
});
|
|
574
|
+
return actions;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
private isGenericRecommendation(value: string): boolean {
|
|
578
|
+
return /\bdefine the first-pass scope\b/i.test(value)
|
|
579
|
+
|| /\bcreate task records\b/i.test(value)
|
|
580
|
+
|| /\brecord concrete tests\b/i.test(value)
|
|
581
|
+
|| /\bseparate out-of-scope work\b/i.test(value);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private submitSelectedAction(question: ProjectPlanningQuestion, actions: readonly PlanningAnswerAction[]): void {
|
|
585
|
+
const action = actions[this.clampActionIndex(actions.length)];
|
|
586
|
+
if (!action || action.disabled || !action.answer.trim()) {
|
|
587
|
+
this.setError('Type an answer or choose a non-empty answer option.');
|
|
588
|
+
this.requestRender();
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (action.kind === 'approve') {
|
|
592
|
+
this.approveExecution();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (action.kind === 'dismiss') {
|
|
596
|
+
this.pausePlanning(question);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (!this.submitAnswer) {
|
|
600
|
+
this.setError('Planning answer submission is not wired in this runtime.');
|
|
601
|
+
this.requestRender();
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
void (async () => {
|
|
605
|
+
try {
|
|
606
|
+
await this.persistQuestionIfNeeded(question);
|
|
607
|
+
this.draftAnswer = '';
|
|
608
|
+
this.submitAnswer?.(action.answer.trim());
|
|
609
|
+
this.refresh(true);
|
|
610
|
+
this.registerTimer(setTimeout(() => this.refresh(true), 250));
|
|
611
|
+
} catch (err) {
|
|
612
|
+
this.setError(err instanceof Error ? err.message : String(err));
|
|
613
|
+
this.requestRender();
|
|
614
|
+
}
|
|
615
|
+
})();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
private async persistQuestionIfNeeded(question: ProjectPlanningQuestion): Promise<void> {
|
|
619
|
+
const state = this.snapshot?.state;
|
|
620
|
+
if (!state) return;
|
|
621
|
+
if (state.openQuestions.some((entry) => entry.id === question.id)) return;
|
|
622
|
+
await this.service.upsertState({
|
|
623
|
+
projectId: this.projectId,
|
|
624
|
+
state: {
|
|
625
|
+
...state,
|
|
626
|
+
openQuestions: [
|
|
627
|
+
{ ...question, status: question.status ?? 'open' },
|
|
628
|
+
...state.openQuestions,
|
|
629
|
+
],
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private pausePlanning(question: ProjectPlanningQuestion): void {
|
|
635
|
+
const state = this.snapshot?.state;
|
|
636
|
+
if (!state) {
|
|
637
|
+
this.dismissPlanning?.();
|
|
638
|
+
this.requestRender();
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
void (async () => {
|
|
642
|
+
try {
|
|
643
|
+
await this.service.upsertState({
|
|
644
|
+
projectId: this.projectId,
|
|
645
|
+
state: {
|
|
646
|
+
...state,
|
|
647
|
+
openQuestions: state.openQuestions.map((entry) =>
|
|
648
|
+
entry.id === question.id
|
|
649
|
+
? { ...entry, status: entry.status ?? 'open' }
|
|
650
|
+
: entry,
|
|
651
|
+
),
|
|
652
|
+
metadata: {
|
|
653
|
+
...(state.metadata ?? {}),
|
|
654
|
+
active: false,
|
|
655
|
+
pausedAt: Date.now(),
|
|
656
|
+
pausedFrom: 'project-planning-panel',
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
this.dismissPlanning?.();
|
|
661
|
+
this.refresh(true);
|
|
662
|
+
} catch (err) {
|
|
663
|
+
this.setError(err instanceof Error ? err.message : String(err));
|
|
664
|
+
this.requestRender();
|
|
665
|
+
}
|
|
666
|
+
})();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private clampActionIndex(count: number): number {
|
|
670
|
+
if (count <= 0) return 0;
|
|
671
|
+
return Math.max(0, Math.min(count - 1, this.selectedActionIndex));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private isPrintableKey(key: string): boolean {
|
|
675
|
+
return key.length === 1 && key >= ' ';
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private compact(text: string): string {
|
|
679
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
680
|
+
return normalized.length > 86 ? `${normalized.slice(0, 83)}...` : normalized;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
private approveExecution(): void {
|
|
684
|
+
const state = this.snapshot?.state;
|
|
685
|
+
if (!state) {
|
|
686
|
+
this.setError('No planning state exists to approve.');
|
|
687
|
+
this.requestRender();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
void (async () => {
|
|
691
|
+
try {
|
|
692
|
+
await this.service.upsertState({
|
|
693
|
+
projectId: this.projectId,
|
|
694
|
+
state: {
|
|
695
|
+
...state,
|
|
696
|
+
executionApproved: true,
|
|
697
|
+
metadata: {
|
|
698
|
+
...(state.metadata ?? {}),
|
|
699
|
+
approvedFrom: 'project-planning-panel',
|
|
700
|
+
approvedAt: Date.now(),
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
this.refresh(true);
|
|
705
|
+
} catch (err) {
|
|
706
|
+
this.setError(err instanceof Error ? err.message : String(err));
|
|
707
|
+
this.requestRender();
|
|
708
|
+
}
|
|
709
|
+
})();
|
|
710
|
+
}
|
|
711
|
+
}
|