@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,331 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { type Line } from '../types/grid.ts';
|
|
3
|
+
import { ModalFactory } from './modal-factory.ts';
|
|
4
|
+
import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
5
|
+
import type { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents';
|
|
6
|
+
import type { WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
|
|
7
|
+
import { formatDuration } from './modal-utils.ts';
|
|
8
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
9
|
+
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
10
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
11
|
+
|
|
12
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
const TOKENS_PER_TOOL_CALL = 400;
|
|
15
|
+
const MAX_LOG_ENTRIES = 10;
|
|
16
|
+
const AGENT_ID_DISPLAY_LENGTH = 16;
|
|
17
|
+
|
|
18
|
+
export interface AgentDetailModalDeps {
|
|
19
|
+
readonly agentManager: Pick<AgentManager, 'getStatus'>;
|
|
20
|
+
readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
|
|
21
|
+
readonly sessionLogPathResolver: (agentId: string) => string;
|
|
22
|
+
/** Optional — when supplied, constraint data from the agent's WRFC chain is shown (SDK 0.23.0). */
|
|
23
|
+
readonly wrfcController?: Pick<WrfcController, 'getChain'>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── AgentDetailModal ─────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* AgentDetailModal — deep-view modal for a single running/completed agent.
|
|
30
|
+
*
|
|
31
|
+
* Displays task description, template, model, status, duration, tool-call
|
|
32
|
+
* count, estimated token usage, recent messages from AgentMessageBus, and
|
|
33
|
+
* the agent's current progress note.
|
|
34
|
+
*/
|
|
35
|
+
export class AgentDetailModal {
|
|
36
|
+
public active = false;
|
|
37
|
+
public agentId: string | null = null;
|
|
38
|
+
|
|
39
|
+
/** Cached JSONL log entries, loaded on open(). */
|
|
40
|
+
public logEntries: Record<string, unknown>[] = [];
|
|
41
|
+
public logTotal = 0;
|
|
42
|
+
|
|
43
|
+
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
44
|
+
private onRefresh: (() => void) | null = null;
|
|
45
|
+
|
|
46
|
+
constructor(readonly deps: AgentDetailModalDeps) {}
|
|
47
|
+
|
|
48
|
+
/** Set a callback to trigger re-render when log data updates. */
|
|
49
|
+
setOnRefresh(fn: () => void): void {
|
|
50
|
+
this.onRefresh = fn;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
open(agentId: string): void {
|
|
54
|
+
this.agentId = agentId;
|
|
55
|
+
this.active = true;
|
|
56
|
+
this.logEntries = [];
|
|
57
|
+
this.logTotal = 0;
|
|
58
|
+
this.loadLog().catch((err) => { logger.debug('agent detail log load failed', { err }); });
|
|
59
|
+
// Auto-refresh log every 500ms while modal is open
|
|
60
|
+
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
|
61
|
+
this.refreshTimer = setInterval(() => {
|
|
62
|
+
this.loadLog().then(() => this.onRefresh?.()).catch((err) => { logger.debug('agent detail log refresh tick failed', { err }); });
|
|
63
|
+
}, 500);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
close(): void {
|
|
67
|
+
this.active = false;
|
|
68
|
+
this.agentId = null;
|
|
69
|
+
this.logEntries = [];
|
|
70
|
+
this.logTotal = 0;
|
|
71
|
+
if (this.refreshTimer) {
|
|
72
|
+
clearInterval(this.refreshTimer);
|
|
73
|
+
this.refreshTimer = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async loadLog(): Promise<void> {
|
|
78
|
+
if (!this.agentId) { this.logEntries = []; this.logTotal = 0; return; }
|
|
79
|
+
try {
|
|
80
|
+
const sessionFile = this.deps.sessionLogPathResolver(this.agentId);
|
|
81
|
+
const logContent = await readFile(sessionFile, 'utf-8');
|
|
82
|
+
const logLines = logContent.trim().split('\n');
|
|
83
|
+
this.logTotal = logLines.length;
|
|
84
|
+
const parsed = logLines.slice(-MAX_LOG_ENTRIES).map(line => {
|
|
85
|
+
try { return JSON.parse(line) as Record<string, unknown>; } catch { return null; }
|
|
86
|
+
});
|
|
87
|
+
const failedCount = parsed.filter(e => e === null).length;
|
|
88
|
+
if (failedCount > 0) {
|
|
89
|
+
logger.debug('AgentDetailModal: skipped malformed JSONL lines', { count: failedCount });
|
|
90
|
+
}
|
|
91
|
+
this.logEntries = parsed.filter((e): e is Record<string, unknown> => e !== null);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
94
|
+
if (code !== 'ENOENT') {
|
|
95
|
+
logger.debug('AgentDetailModal: failed to load session log', { error: summarizeError(err) });
|
|
96
|
+
}
|
|
97
|
+
this.logEntries = [];
|
|
98
|
+
this.logTotal = 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
/** Rough token estimate: toolCallCount * avg tokens per tool exchange. */
|
|
106
|
+
function estimateTokens(toolCallCount: number): number {
|
|
107
|
+
return toolCallCount * TOKENS_PER_TOOL_CALL;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── renderAgentDetailModal ───────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Render the agent detail modal as Line[] for overlay in the viewport.
|
|
114
|
+
*
|
|
115
|
+
* Shows a deep view of the selected agent: task, template, model, status,
|
|
116
|
+
* duration, tool call count, token estimate, recent bus messages, and
|
|
117
|
+
* progress text.
|
|
118
|
+
*
|
|
119
|
+
* @param modal AgentDetailModal state
|
|
120
|
+
* @param width Terminal width
|
|
121
|
+
*/
|
|
122
|
+
export function renderAgentDetailModal(
|
|
123
|
+
modal: AgentDetailModal,
|
|
124
|
+
width: number,
|
|
125
|
+
viewportHeight = 24,
|
|
126
|
+
): Line[] {
|
|
127
|
+
if (!modal.agentId) return [];
|
|
128
|
+
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
129
|
+
margin: 2,
|
|
130
|
+
maxWidth: width - 4,
|
|
131
|
+
chromeRows: 6,
|
|
132
|
+
minContentRows: 10,
|
|
133
|
+
maxContentRows: 22,
|
|
134
|
+
});
|
|
135
|
+
const targetContentRows = Math.max(18, Math.min(22, getStableOverlayContentRows(metrics.contentRows, 12) + 8));
|
|
136
|
+
|
|
137
|
+
const rec = modal.deps.agentManager.getStatus(modal.agentId);
|
|
138
|
+
if (!rec) {
|
|
139
|
+
return ModalFactory.createModal({
|
|
140
|
+
title: 'Agent Detail',
|
|
141
|
+
width: metrics.boxWidth,
|
|
142
|
+
margin: metrics.margin,
|
|
143
|
+
targetContentRows,
|
|
144
|
+
sections: [
|
|
145
|
+
{ type: 'text', content: '(agent not found)' },
|
|
146
|
+
],
|
|
147
|
+
hints: ['[Esc] Close'],
|
|
148
|
+
}, width);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
const elapsedMs = (rec.completedAt ?? now) - rec.startedAt;
|
|
153
|
+
const tokenEst = estimateTokens(rec.toolCallCount);
|
|
154
|
+
|
|
155
|
+
// ── Build sections ────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
const sections: import('./modal-factory.ts').ModalSection[] = [];
|
|
158
|
+
|
|
159
|
+
// Task — show first line only, capped at 120 chars
|
|
160
|
+
const taskFirstLine = rec.task.split('\n')[0].replace(/^(WRFC\s+(Fix|Review)\s+Request\s*)/i, '').trim();
|
|
161
|
+
const taskDisplay = taskFirstLine.length > 120 ? taskFirstLine.slice(0, 117) + '\u2026' : taskFirstLine;
|
|
162
|
+
sections.push({
|
|
163
|
+
type: 'text',
|
|
164
|
+
content: `Task: ${taskDisplay}`,
|
|
165
|
+
style: { bold: true },
|
|
166
|
+
});
|
|
167
|
+
sections.push({ type: 'separator' });
|
|
168
|
+
|
|
169
|
+
// Metadata grid
|
|
170
|
+
const modelStr = rec.model ? `${rec.provider ?? ''}/${rec.model}` : (rec.provider ?? '(default)');
|
|
171
|
+
sections.push({ type: 'text', content: `Template : ${rec.template}` });
|
|
172
|
+
sections.push({ type: 'text', content: `Model : ${modelStr}` });
|
|
173
|
+
sections.push({ type: 'text', content: `Status : ${rec.status}` });
|
|
174
|
+
sections.push({ type: 'text', content: `Duration : ${formatDuration(elapsedMs)}` });
|
|
175
|
+
sections.push({ type: 'separator' });
|
|
176
|
+
|
|
177
|
+
// Metrics
|
|
178
|
+
sections.push({ type: 'text', content: `Tool calls : ${rec.toolCallCount}` });
|
|
179
|
+
sections.push({ type: 'text', content: `Est tokens : ~${tokenEst.toLocaleString()}` });
|
|
180
|
+
|
|
181
|
+
// SDK 0.23.0: systemPromptAddendum indicator — confirms WRFC constraint addendum was injected
|
|
182
|
+
if (rec.systemPromptAddendum) {
|
|
183
|
+
sections.push({
|
|
184
|
+
type: 'text',
|
|
185
|
+
content: 'Addendum : yes (WRFC constraint layer injected)',
|
|
186
|
+
style: { fg: '#aaffee' },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// SDK 0.23.0: constraint data from WRFC chain (engineer constraints + reviewer findings)
|
|
191
|
+
if (rec.wrfcId && modal.deps.wrfcController) {
|
|
192
|
+
try {
|
|
193
|
+
const chain = modal.deps.wrfcController.getChain(rec.wrfcId);
|
|
194
|
+
if (chain && chain.constraints.length > 0) {
|
|
195
|
+
sections.push({ type: 'separator' });
|
|
196
|
+
sections.push({
|
|
197
|
+
type: 'text',
|
|
198
|
+
content: `Constraints (${chain.constraints.length}):`,
|
|
199
|
+
style: { dim: true },
|
|
200
|
+
});
|
|
201
|
+
for (const c of chain.constraints) {
|
|
202
|
+
const text = c.text.length > 80 ? c.text.slice(0, 77) + '…' : c.text;
|
|
203
|
+
sections.push({
|
|
204
|
+
type: 'text',
|
|
205
|
+
content: ` [${c.id}] ${text}`,
|
|
206
|
+
style: { fg: '246' },
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// Reviewer constraint findings (if review has completed)
|
|
210
|
+
const findings = chain.reviewerReport?.constraintFindings;
|
|
211
|
+
if (findings && findings.length > 0) {
|
|
212
|
+
const unsatisfied = findings.filter((f) => !f.satisfied);
|
|
213
|
+
sections.push({
|
|
214
|
+
type: 'text',
|
|
215
|
+
content: `Findings : ${findings.length} checked, ${unsatisfied.length} unsatisfied`,
|
|
216
|
+
style: { fg: unsatisfied.length > 0 ? '#ff6666' : '#44ff88' },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
// wrfcController.getChain throws when chain not found — normal during teardown
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Progress
|
|
226
|
+
if (rec.progress) {
|
|
227
|
+
sections.push({ type: 'separator' });
|
|
228
|
+
sections.push({
|
|
229
|
+
type: 'text',
|
|
230
|
+
content: `Progress: ${rec.progress}`,
|
|
231
|
+
style: { fg: '#00ffcc' },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Error
|
|
236
|
+
if (rec.error) {
|
|
237
|
+
sections.push({ type: 'separator' });
|
|
238
|
+
sections.push({
|
|
239
|
+
type: 'text',
|
|
240
|
+
content: `Error: ${rec.error}`,
|
|
241
|
+
style: { fg: '#ff6666' },
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Recent messages from AgentMessageBus
|
|
246
|
+
const recentMessages = modal.deps.agentMessageBus
|
|
247
|
+
.getMessages(modal.agentId)
|
|
248
|
+
.slice(-4); // last 4 messages
|
|
249
|
+
|
|
250
|
+
if (recentMessages.length > 0) {
|
|
251
|
+
sections.push({ type: 'separator' });
|
|
252
|
+
sections.push({
|
|
253
|
+
type: 'text',
|
|
254
|
+
content: `Recent messages (${recentMessages.length}):`,
|
|
255
|
+
style: { dim: true },
|
|
256
|
+
});
|
|
257
|
+
for (const msg of recentMessages) {
|
|
258
|
+
const fromLabel = msg.from === '*' ? 'broadcast' : msg.from.slice(0, 12);
|
|
259
|
+
const preview = msg.content.length > 50
|
|
260
|
+
? msg.content.slice(0, 47) + '\u2026'
|
|
261
|
+
: msg.content;
|
|
262
|
+
sections.push({
|
|
263
|
+
type: 'text',
|
|
264
|
+
content: ` [${fromLabel}] ${preview}`,
|
|
265
|
+
style: { fg: '246' },
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Execution history from cached JSONL session log (loaded on open())
|
|
271
|
+
if (modal.logEntries.length > 0) {
|
|
272
|
+
sections.push({ type: 'separator' });
|
|
273
|
+
sections.push({
|
|
274
|
+
type: 'text',
|
|
275
|
+
content: `Execution Log (${modal.logTotal} events, showing last ${modal.logEntries.length}):`,
|
|
276
|
+
style: { dim: true },
|
|
277
|
+
});
|
|
278
|
+
for (const entry of modal.logEntries) {
|
|
279
|
+
const entryType = String(entry.type ?? 'unknown');
|
|
280
|
+
const rawTs = entry.timestamp;
|
|
281
|
+
const ts = typeof rawTs === 'string' && rawTs.length >= 19
|
|
282
|
+
? rawTs.slice(11, 19)
|
|
283
|
+
: '';
|
|
284
|
+
let detail = '';
|
|
285
|
+
if (entryType === 'tool_execution') detail = ` ${entry.toolName}`;
|
|
286
|
+
if (entryType === 'llm_response') detail = ` (${entry.toolCallCount} tools, ${entry.contentLength} chars)`;
|
|
287
|
+
if (entryType === 'session_end') detail = ` [${entry.status}]`;
|
|
288
|
+
sections.push({
|
|
289
|
+
type: 'text',
|
|
290
|
+
content: ` ${ts} ${entryType}${detail}`,
|
|
291
|
+
style: { fg: '246' },
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Streaming content — show live output when agent is actively streaming
|
|
297
|
+
const STREAMING_MAX_CHARS = 500;
|
|
298
|
+
if (rec.status === 'running' && rec.streamingContent) {
|
|
299
|
+
const content = rec.streamingContent;
|
|
300
|
+
const truncated = content.length > STREAMING_MAX_CHARS;
|
|
301
|
+
const display = truncated ? content.slice(-STREAMING_MAX_CHARS) : content;
|
|
302
|
+
sections.push({ type: 'separator' });
|
|
303
|
+
sections.push({
|
|
304
|
+
type: 'text',
|
|
305
|
+
content: truncated
|
|
306
|
+
? `Streaming (last ${STREAMING_MAX_CHARS} of ${content.length} chars \u2191 scroll for more):`
|
|
307
|
+
: 'Streaming:',
|
|
308
|
+
style: { fg: '#00ffcc', dim: true },
|
|
309
|
+
});
|
|
310
|
+
// Split into display lines, capped at width for readability
|
|
311
|
+
const maxLineWidth = Math.max(width - 10, 40);
|
|
312
|
+
const streamLines = display.split('\n');
|
|
313
|
+
for (const line of streamLines) {
|
|
314
|
+
const trimmed = line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + '\u2026' : line;
|
|
315
|
+
sections.push({
|
|
316
|
+
type: 'text',
|
|
317
|
+
content: ` ${trimmed}`,
|
|
318
|
+
style: { fg: '#aaffee' },
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return ModalFactory.createModal({
|
|
324
|
+
title: `Agent: ${rec.id.slice(0, AGENT_ID_DISPLAY_LENGTH)}`,
|
|
325
|
+
width: metrics.boxWidth,
|
|
326
|
+
margin: metrics.margin,
|
|
327
|
+
targetContentRows,
|
|
328
|
+
sections,
|
|
329
|
+
hints: ['[Esc] Close'],
|
|
330
|
+
}, width);
|
|
331
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentWorkspace,
|
|
3
|
+
AgentWorkspaceAction,
|
|
4
|
+
AgentWorkspaceActionResult,
|
|
5
|
+
AgentWorkspaceCategory,
|
|
6
|
+
AgentWorkspaceRuntimeSnapshot,
|
|
7
|
+
} from '../input/agent-workspace.ts';
|
|
8
|
+
import type { Line } from '../types/grid.ts';
|
|
9
|
+
import { wrapText } from '../utils/terminal-width.ts';
|
|
10
|
+
import { GLYPHS } from './ui-primitives.ts';
|
|
11
|
+
import {
|
|
12
|
+
getFullscreenWorkspaceMetrics,
|
|
13
|
+
padDisplay,
|
|
14
|
+
renderFullscreenWorkspace,
|
|
15
|
+
stableWindow,
|
|
16
|
+
WORKSPACE_PALETTE as PALETTE,
|
|
17
|
+
type WorkspaceRow,
|
|
18
|
+
} from './fullscreen-workspace.ts';
|
|
19
|
+
|
|
20
|
+
function safetyColor(action: AgentWorkspaceAction): string {
|
|
21
|
+
if (action.safety === 'safe') return PALETTE.good;
|
|
22
|
+
if (action.safety === 'read-only') return PALETTE.info;
|
|
23
|
+
if (action.safety === 'delegates') return PALETTE.warn;
|
|
24
|
+
return PALETTE.bad;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function actionResultColor(result: AgentWorkspaceActionResult): string {
|
|
28
|
+
if (result.kind === 'blocked' || result.kind === 'error') return PALETTE.bad;
|
|
29
|
+
if (result.kind === 'dispatched') return PALETTE.info;
|
|
30
|
+
if (result.kind === 'refreshed') return PALETTE.good;
|
|
31
|
+
return PALETTE.muted;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[] {
|
|
35
|
+
const rows: WorkspaceRow[] = [];
|
|
36
|
+
let selectedRenderedIndex = 0;
|
|
37
|
+
let lastGroup = '';
|
|
38
|
+
|
|
39
|
+
workspace.categories.forEach((category, index) => {
|
|
40
|
+
if (category.group !== lastGroup) {
|
|
41
|
+
rows.push({ text: category.group, kind: 'group', bold: true });
|
|
42
|
+
lastGroup = category.group;
|
|
43
|
+
}
|
|
44
|
+
const selected = index === workspace.selectedCategoryIndex;
|
|
45
|
+
if (selected) selectedRenderedIndex = rows.length;
|
|
46
|
+
const marker = selected ? GLYPHS.navigation.selected : ' ';
|
|
47
|
+
rows.push({
|
|
48
|
+
text: ` ${marker} ${category.label}`,
|
|
49
|
+
selected: selected && workspace.focusPane === 'categories',
|
|
50
|
+
kind: 'item',
|
|
51
|
+
fg: selected ? PALETTE.text : PALETTE.muted,
|
|
52
|
+
bold: selected,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const visible = Math.max(1, height);
|
|
57
|
+
const window = stableWindow(rows.length, selectedRenderedIndex, visible);
|
|
58
|
+
const visibleRows = rows.slice(window.start, window.end);
|
|
59
|
+
if (window.start > 0 && visibleRows.length > 0) {
|
|
60
|
+
visibleRows[0] = { text: `${GLYPHS.navigation.moreAbove} ${window.start} more row(s) above`, kind: 'more', fg: PALETTE.dim, dim: true };
|
|
61
|
+
}
|
|
62
|
+
if (window.end < rows.length && visibleRows.length > 0) {
|
|
63
|
+
visibleRows[visibleRows.length - 1] = { text: `${GLYPHS.navigation.moreBelow} ${rows.length - window.end} more row(s) below`, kind: 'more', fg: PALETTE.dim, dim: true };
|
|
64
|
+
}
|
|
65
|
+
while (visibleRows.length < height) visibleRows.push({ text: '', kind: 'empty' });
|
|
66
|
+
return visibleRows.slice(0, height);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function actionCommand(action: AgentWorkspaceAction): string {
|
|
70
|
+
return action.command ?? '(guidance)';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type ContextLine = { readonly text: string; readonly fg?: string; readonly bold?: boolean; readonly dim?: boolean };
|
|
74
|
+
|
|
75
|
+
function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
|
|
76
|
+
if (!snapshot) return [{ text: 'Runtime context is not loaded yet.', fg: PALETTE.warn }];
|
|
77
|
+
const base: ContextLine[] = [{ text: 'Live Agent Context', fg: PALETTE.title, bold: true }];
|
|
78
|
+
if (category.id === 'home') {
|
|
79
|
+
base.push(
|
|
80
|
+
{ text: `Chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`, fg: PALETTE.info },
|
|
81
|
+
{ text: `Session: ${snapshot.sessionId}`, fg: PALETTE.muted },
|
|
82
|
+
{ text: `Policy: ${snapshot.executionPolicy}; WRFC ${snapshot.wrfcPolicy}`, fg: PALETTE.good },
|
|
83
|
+
);
|
|
84
|
+
} else if (category.id === 'setup') {
|
|
85
|
+
base.push(
|
|
86
|
+
{ text: `External daemon: ${snapshot.daemonBaseUrl}`, fg: PALETTE.info },
|
|
87
|
+
{ text: `Daemon ownership: ${snapshot.daemonOwnership}; Agent never starts or restarts it`, fg: PALETTE.good },
|
|
88
|
+
{ text: `Workspace: ${snapshot.workingDirectory}`, fg: PALETTE.muted },
|
|
89
|
+
{ text: `Home: ${snapshot.homeDirectory}`, fg: PALETTE.muted },
|
|
90
|
+
);
|
|
91
|
+
} else if (category.id === 'knowledge') {
|
|
92
|
+
base.push(
|
|
93
|
+
{ text: `Route family: ${snapshot.knowledgeRoute}/{status,ask,search}`, fg: PALETTE.info },
|
|
94
|
+
{ text: `Isolation: ${snapshot.knowledgeIsolation}; no default Knowledge/Wiki or HomeGraph fallback`, fg: PALETTE.good },
|
|
95
|
+
{ text: 'Agent-owned content appears here only after explicit Agent knowledge ingestion.', fg: PALETTE.muted },
|
|
96
|
+
);
|
|
97
|
+
} else if (category.id === 'memory') {
|
|
98
|
+
base.push(
|
|
99
|
+
{ text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
|
|
100
|
+
{ text: 'Durable memory, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
|
|
101
|
+
{ text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
|
|
102
|
+
);
|
|
103
|
+
} else if (category.id === 'work') {
|
|
104
|
+
base.push(
|
|
105
|
+
{ text: 'Work plan and approvals are read or explicitly confirmed through public operator routes.', fg: PALETTE.info },
|
|
106
|
+
{ text: 'This workspace does not approve, deny, cancel, or mutate requests by selection alone.', fg: PALETTE.good },
|
|
107
|
+
);
|
|
108
|
+
} else if (category.id === 'automation') {
|
|
109
|
+
base.push(
|
|
110
|
+
{ text: 'Automation and schedules default to read-only observability.', fg: PALETTE.info },
|
|
111
|
+
{ text: 'Run/pause/resume/cancel/retry require exact explicit commands and confirmation.', fg: PALETTE.warn },
|
|
112
|
+
);
|
|
113
|
+
} else if (category.id === 'delegate') {
|
|
114
|
+
base.push(
|
|
115
|
+
{ text: 'Build/fix/review work is handed to GoodVibes TUI/shared-session contracts.', fg: PALETTE.info },
|
|
116
|
+
{ text: `WRFC policy: ${snapshot.wrfcPolicy}`, fg: PALETTE.warn },
|
|
117
|
+
{ text: 'Agent does not spawn local Engineer/Reviewer/Tester roots.', fg: PALETTE.good },
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (snapshot.warnings.length > 0) {
|
|
121
|
+
base.push({ text: '', dim: true }, { text: 'Warnings', fg: PALETTE.warn, bold: true });
|
|
122
|
+
for (const warning of snapshot.warnings) base.push({ text: warning, fg: PALETTE.warn });
|
|
123
|
+
}
|
|
124
|
+
return base;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction | null, width: number): WorkspaceRow[] {
|
|
128
|
+
const lines: ContextLine[] = [
|
|
129
|
+
{ text: category.label, fg: PALETTE.title, bold: true },
|
|
130
|
+
{ text: category.summary, fg: PALETTE.subtitle },
|
|
131
|
+
{ text: '' },
|
|
132
|
+
{ text: category.detail, fg: PALETTE.text },
|
|
133
|
+
{ text: '' },
|
|
134
|
+
...snapshotLines(category, workspace.runtimeSnapshot),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
if (action) {
|
|
138
|
+
lines.push(
|
|
139
|
+
{ text: '' },
|
|
140
|
+
{ text: `Selected: ${action.label}`, fg: PALETTE.title, bold: true },
|
|
141
|
+
{ text: action.detail, fg: PALETTE.text },
|
|
142
|
+
{ text: `Command: ${actionCommand(action)}`, fg: action.kind === 'command' ? PALETTE.info : PALETTE.muted },
|
|
143
|
+
{ text: `Safety: ${action.safety}`, fg: safetyColor(action) },
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (workspace.lastActionResult) {
|
|
148
|
+
lines.push(
|
|
149
|
+
{ text: '' },
|
|
150
|
+
{ text: 'Action Result', fg: PALETTE.title, bold: true },
|
|
151
|
+
{ text: workspace.lastActionResult.title, fg: actionResultColor(workspace.lastActionResult), bold: true },
|
|
152
|
+
{ text: workspace.lastActionResult.detail, fg: PALETTE.text },
|
|
153
|
+
);
|
|
154
|
+
if (workspace.lastActionResult.command) {
|
|
155
|
+
lines.push({ text: `Command: ${workspace.lastActionResult.command}`, fg: PALETTE.muted });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return lines.flatMap((entry): WorkspaceRow[] => {
|
|
160
|
+
if (entry.text.length === 0) return [{ text: '', kind: 'empty', dim: true }];
|
|
161
|
+
return wrapText(entry.text, Math.max(1, width)).map((text, index): WorkspaceRow => ({
|
|
162
|
+
text,
|
|
163
|
+
fg: entry.fg,
|
|
164
|
+
bold: entry.bold && index === 0,
|
|
165
|
+
dim: entry.dim,
|
|
166
|
+
}));
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildActionRows(workspace: AgentWorkspace, width: number, height: number): WorkspaceRow[] {
|
|
171
|
+
const rows: WorkspaceRow[] = [];
|
|
172
|
+
const labelWidth = Math.min(28, Math.max(16, Math.floor(width * 0.30)));
|
|
173
|
+
const safetyWidth = 10;
|
|
174
|
+
const commandWidth = Math.max(10, width - labelWidth - safetyWidth - 9);
|
|
175
|
+
rows.push({
|
|
176
|
+
text: ` ${padDisplay('Action', labelWidth)} ${padDisplay('Safety', safetyWidth)} ${padDisplay('Command', commandWidth)}`,
|
|
177
|
+
fg: PALETTE.muted,
|
|
178
|
+
bold: true,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const actions = workspace.actions;
|
|
182
|
+
const visible = Math.max(1, height - 2);
|
|
183
|
+
const window = stableWindow(actions.length, workspace.selectedActionIndex, visible);
|
|
184
|
+
if (window.start > 0) rows.push({ text: `${GLYPHS.navigation.moreAbove} ${window.start} more action(s) above`, kind: 'more', fg: PALETTE.dim, dim: true });
|
|
185
|
+
|
|
186
|
+
for (let index = window.start; index < window.end; index += 1) {
|
|
187
|
+
const action = actions[index]!;
|
|
188
|
+
const selected = index === workspace.selectedActionIndex;
|
|
189
|
+
const marker = selected ? GLYPHS.navigation.selected : ' ';
|
|
190
|
+
rows.push({
|
|
191
|
+
text: `${marker} ${padDisplay(action.label, labelWidth)} ${padDisplay(action.safety, safetyWidth)} ${padDisplay(actionCommand(action), commandWidth)}`,
|
|
192
|
+
selected: selected && workspace.focusPane === 'actions',
|
|
193
|
+
fg: safetyColor(action),
|
|
194
|
+
bold: selected,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (window.end < actions.length) rows.push({ text: `${GLYPHS.navigation.moreBelow} ${actions.length - window.end} more action(s) below`, kind: 'more', fg: PALETTE.dim, dim: true });
|
|
199
|
+
rows.push({ text: '' });
|
|
200
|
+
rows.push({ text: `Status: ${workspace.status}`, fg: PALETTE.muted });
|
|
201
|
+
if (workspace.lastActionResult) {
|
|
202
|
+
rows.push({ text: '' });
|
|
203
|
+
rows.push({ text: `Action Result: ${workspace.lastActionResult.title}`, fg: actionResultColor(workspace.lastActionResult), bold: true });
|
|
204
|
+
for (const line of wrapText(workspace.lastActionResult.detail, Math.max(1, width - 2))) {
|
|
205
|
+
rows.push({ text: ` ${line}`, fg: PALETTE.text });
|
|
206
|
+
}
|
|
207
|
+
if (workspace.lastActionResult.command) {
|
|
208
|
+
rows.push({ text: ` Command: ${workspace.lastActionResult.command}`, fg: PALETTE.muted });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
while (rows.length < height) rows.push({ text: '', kind: 'empty' });
|
|
213
|
+
return rows.slice(0, height);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function footerText(workspace: AgentWorkspace): string {
|
|
217
|
+
const focus = workspace.focusPane === 'categories' ? 'categories' : 'actions';
|
|
218
|
+
return `Agent workspace · focus ${focus} · Up/Down navigate · Left/Right pane · Enter open/action · R refresh · Esc close`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, height: number): Line[] {
|
|
222
|
+
const metrics = getFullscreenWorkspaceMetrics({ width, height, leftWidth: width < 90 ? undefined : 30, contextRatio: 0.48, minContextRows: 8 });
|
|
223
|
+
const category = workspace.selectedCategory;
|
|
224
|
+
const action = workspace.selectedAction;
|
|
225
|
+
|
|
226
|
+
return renderFullscreenWorkspace({
|
|
227
|
+
width,
|
|
228
|
+
height,
|
|
229
|
+
title: 'GoodVibes Agent / Operator Workspace',
|
|
230
|
+
stateLabel: workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
|
|
231
|
+
leftHeader: 'Operator Areas',
|
|
232
|
+
mainHeader: `${category.label} · ${category.actions.length} action(s)`,
|
|
233
|
+
leftRows: buildLeftRows(workspace, metrics.bodyRows),
|
|
234
|
+
contextRows: buildContextRows(workspace, category, action, metrics.contextWidth),
|
|
235
|
+
controlRows: buildActionRows(workspace, metrics.contextWidth, metrics.controlRows),
|
|
236
|
+
footer: footerText(workspace),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI sanitizer for untrusted content entering the renderer.
|
|
3
|
+
*
|
|
4
|
+
* The TUI grid renders content character-by-character via writeStyledText,
|
|
5
|
+
* which already drops zero-width characters (including ESC \x1b) by checking
|
|
6
|
+
* display width. However, that is incidental protection — not a contract.
|
|
7
|
+
* This module provides explicit, intentional sanitization.
|
|
8
|
+
*
|
|
9
|
+
* Strategy:
|
|
10
|
+
* - STRIP all non-SGR escape sequences (cursor moves, OSC, BEL, alt-screen,
|
|
11
|
+
* DECSET/private mode, and any other CSI/ESC sequences).
|
|
12
|
+
* - PRESERVE SGR color/style codes (\x1b[<params>m) — used legitimately by
|
|
13
|
+
* the TUI's own colorized output paths.
|
|
14
|
+
* - STRIP bare BEL (\x07) characters.
|
|
15
|
+
*
|
|
16
|
+
* Safe SGR pattern: \x1b[ followed by digits/semicolons, ending in 'm'.
|
|
17
|
+
* Everything else that starts with \x1b is dangerous and stripped.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Matches safe SGR sequences: ESC [ <digits/semicolons> m
|
|
21
|
+
const SGR_PATTERN = /\x1b\[([0-9;]*)m/g;
|
|
22
|
+
|
|
23
|
+
// Matches ALL CSI sequences: ESC [ ... <final byte 0x40-0x7E>
|
|
24
|
+
const CSI_SEQUENCE = /\x1b\[[\x20-\x3f]*[\x40-\x7e]/g;
|
|
25
|
+
|
|
26
|
+
// Matches OSC sequences: ESC ] ... (ESC \ or BEL)
|
|
27
|
+
const OSC_SEQUENCE = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
28
|
+
|
|
29
|
+
// Matches other ESC sequences (ESC + single character that is not '[' or ']')
|
|
30
|
+
const ESC_OTHER = /\x1b[^\[\]]/g;
|
|
31
|
+
|
|
32
|
+
// Matches standalone BEL
|
|
33
|
+
const BEL = /\x07/g;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Strip dangerous ANSI escape sequences from untrusted content.
|
|
37
|
+
*
|
|
38
|
+
* Preserves SGR color codes (\x1b[<n>m). Removes:
|
|
39
|
+
* - Cursor movement CSI sequences (\x1b[<n>A/B/C/D, \x1b[H, etc.)
|
|
40
|
+
* - OSC sequences (\x1b]...\x07 or \x1b]...\x1b\\)
|
|
41
|
+
* - Alt-screen and DECSET private mode (\x1b[?...h/l)
|
|
42
|
+
* - Any other CSI or ESC sequences
|
|
43
|
+
* - Bare BEL (\x07)
|
|
44
|
+
*
|
|
45
|
+
* @param input - Raw string that may contain ANSI escape sequences
|
|
46
|
+
* @returns Sanitized string safe for grid rendering
|
|
47
|
+
*/
|
|
48
|
+
export function stripDangerousAnsi(input: string): string {
|
|
49
|
+
// Step 1: Extract and preserve SGR sequences by replacing them with placeholders,
|
|
50
|
+
// then strip all other escape sequences, then restore SGR sequences.
|
|
51
|
+
// This approach avoids complex negative lookahead regexes.
|
|
52
|
+
|
|
53
|
+
// Collect SGR sequences and replace with unique markers
|
|
54
|
+
const sgrTokens: string[] = [];
|
|
55
|
+
const withPlaceholders = input.replace(SGR_PATTERN, (match) => {
|
|
56
|
+
const idx = sgrTokens.length;
|
|
57
|
+
sgrTokens.push(match);
|
|
58
|
+
return `\x00SGR${idx}\x00`;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Strip all remaining dangerous sequences
|
|
62
|
+
let sanitized = withPlaceholders
|
|
63
|
+
.replace(CSI_SEQUENCE, '') // removes cursor moves, alt-screen, DECSET, etc.
|
|
64
|
+
.replace(OSC_SEQUENCE, '') // removes OSC
|
|
65
|
+
.replace(ESC_OTHER, '') // removes remaining ESC+char sequences
|
|
66
|
+
.replace(/\x1b/g, '') // removes any leftover bare ESC
|
|
67
|
+
.replace(BEL, ''); // removes BEL
|
|
68
|
+
|
|
69
|
+
// Restore SGR sequences from placeholders
|
|
70
|
+
sanitized = sanitized.replace(/\x00SGR(\d+)\x00/g, (_match, idxStr) => {
|
|
71
|
+
const idx = parseInt(idxStr, 10);
|
|
72
|
+
return sgrTokens[idx] ?? '';
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return sanitized;
|
|
76
|
+
}
|