@pellux/goodvibes-tui 1.16.1 → 1.17.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/CHANGELOG.md +89 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1091 -271
- package/package.json +7 -7
- package/src/cli/completions/generate.ts +5 -4
- package/src/cli/config-overrides.ts +51 -11
- package/src/cli/doctor.ts +4 -5
- package/src/cli/entrypoint.ts +13 -8
- package/src/cli/help.ts +7 -6
- package/src/cli/launch-auto-update.ts +214 -0
- package/src/cli/parser.ts +5 -0
- package/src/cli/status.ts +13 -0
- package/src/cli/surface-command.ts +5 -5
- package/src/cli/tui-startup.ts +60 -97
- package/src/cli/types.ts +2 -0
- package/src/config/index.ts +1 -1
- package/src/config/tui-extension-settings.ts +33 -0
- package/src/core/composer-state.ts +1 -1
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +4 -4
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/stream-event-wiring.ts +8 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-cancellation.ts +1 -1
- package/src/core/turn-event-wiring.ts +8 -8
- package/src/daemon/cli.ts +20 -5
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/service-commands.ts +5 -5
- package/src/export/cost-utils.ts +128 -6
- package/src/input/autocomplete.ts +2 -2
- package/src/input/command-registry.ts +34 -9
- package/src/input/commands/ci-runtime.ts +5 -1
- package/src/input/commands/codebase-runtime.ts +6 -6
- package/src/input/commands/cost-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/intelligence-runtime.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +1 -1
- package/src/input/commands/local-provider-runtime.ts +49 -10
- package/src/input/commands/local-runtime.ts +53 -5
- package/src/input/commands/memory.ts +2 -2
- package/src/input/commands/operator-panel-runtime.ts +5 -5
- package/src/input/commands/permissions-provenance.ts +1 -1
- package/src/input/commands/permissions-runtime.ts +99 -8
- package/src/input/commands/planning-runtime.ts +3 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -1
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/recall-files-git.ts +13 -51
- package/src/input/commands/recall-files.ts +8 -11
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/relay-runtime.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +4 -4
- package/src/input/commands/schedule-runtime.ts +14 -7
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +2 -2
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +46 -68
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/update-runtime.ts +153 -96
- package/src/input/commands/work-plan-runtime.ts +1 -1
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands.ts +0 -2
- package/src/input/config-modal-types.ts +3 -3
- package/src/input/config-modal.ts +10 -11
- package/src/input/feature-unit-layout.ts +93 -82
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/handler-command-route.ts +2 -2
- package/src/input/handler-content-actions.ts +1 -1
- package/src/input/handler-feed-routes.ts +7 -7
- package/src/input/handler-feed.ts +4 -4
- package/src/input/handler-interactions.ts +3 -2
- package/src/input/handler-modal-routes.ts +12 -4
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
- package/src/input/handler-onboarding.ts +3 -2
- package/src/input/handler-picker-routes.ts +26 -13
- package/src/input/handler-shortcuts.ts +6 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +2 -2
- package/src/input/onboarding/onboarding-feature-units.ts +34 -27
- package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
- package/src/input/panel-integration-actions.ts +2 -2
- package/src/input/panel-mouse-geometry.ts +1 -1
- package/src/input/panel-paste-flood-guard.ts +1 -1
- package/src/input/provider-key-intake.ts +167 -0
- package/src/input/sandbox-exec-config.ts +2 -2
- package/src/input/selection-modal.ts +1 -1
- package/src/input/settings-modal-activation.ts +5 -0
- package/src/input/settings-modal-adjustment.ts +4 -31
- package/src/input/settings-modal-data.ts +22 -21
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +23 -16
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +48 -48
- package/src/panels/agent-inspector-shared.ts +4 -4
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/agent.ts +4 -4
- package/src/panels/builtin/development.ts +4 -4
- package/src/panels/builtin/knowledge.ts +1 -1
- package/src/panels/builtin/operations.ts +29 -10
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/builtin-modals.ts +10 -10
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +5 -5
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-panel-format.ts +44 -8
- package/src/panels/fleet-panel.ts +16 -16
- package/src/panels/fleet-read-model.ts +52 -13
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +3 -3
- package/src/panels/fleet-tabs.ts +13 -14
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/git-panel.ts +2 -2
- package/src/panels/index.ts +4 -1
- package/src/panels/local-auth-panel.ts +1 -1
- package/src/panels/modals/hooks-modal.ts +1 -1
- package/src/panels/modals/keybindings-modal.ts +2 -2
- package/src/panels/modals/knowledge-modal.ts +1 -1
- package/src/panels/modals/marketplace-modal.ts +4 -4
- package/src/panels/modals/memory-modal.ts +4 -4
- package/src/panels/modals/modal-surface-helpers.ts +1 -1
- package/src/panels/modals/modal-theme.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +2 -2
- package/src/panels/modals/planning-modal.ts +2 -2
- package/src/panels/modals/plugins-modal.ts +1 -1
- package/src/panels/modals/policy-modal.ts +1 -1
- package/src/panels/modals/security-modal.ts +1 -1
- package/src/panels/modals/skills-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +3 -3
- package/src/panels/notifications-feed.ts +127 -0
- package/src/panels/notifications-panel.ts +160 -0
- package/src/panels/panel-manager.ts +9 -9
- package/src/panels/plugins-panel.ts +4 -4
- package/src/panels/project-planning-answer-actions.ts +1 -1
- package/src/panels/provider-health-routes.ts +2 -2
- package/src/panels/skills-panel.ts +4 -5
- package/src/panels/token-budget-panel.ts +11 -3
- package/src/panels/types.ts +5 -5
- package/src/permissions/broker-approval-card.ts +201 -0
- package/src/permissions/hunk-selection.ts +28 -4
- package/src/permissions/prompt.ts +244 -29
- package/src/renderer/autocomplete-overlay.ts +107 -54
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compaction-history-modal.ts +1 -1
- package/src/renderer/compaction-preview.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/footer-tips.ts +3 -3
- package/src/renderer/hint-grammar.ts +1 -1
- package/src/renderer/modal-factory.ts +1 -1
- package/src/renderer/model-picker-overlay.ts +1 -1
- package/src/renderer/model-workspace.ts +7 -2
- package/src/renderer/panel-composite.ts +2 -2
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/selection-modal-overlay.ts +1 -1
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +14 -5
- package/src/renderer/settings-modal.ts +127 -39
- package/src/renderer/shell-surface.ts +3 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +1 -1
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +6 -1
- package/src/runtime/bootstrap-command-context.ts +4 -1
- package/src/runtime/bootstrap-command-parts.ts +6 -4
- package/src/runtime/bootstrap-core.ts +35 -35
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +6 -5
- package/src/runtime/bootstrap.ts +34 -32
- package/src/runtime/channel-composition.ts +46 -0
- package/src/runtime/code-index-services.ts +3 -4
- package/src/runtime/daemon-attach-notices.ts +120 -0
- package/src/runtime/durability-services.ts +55 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/index.ts +4 -21
- package/src/runtime/legacy-daemon-migration.ts +29 -19
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/notification-dispatch.ts +110 -0
- package/src/runtime/onboarding/apply.ts +3 -29
- package/src/runtime/onboarding/fast-path.ts +39 -6
- package/src/runtime/onboarding/provider-key-capture.ts +126 -0
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +9 -3
- package/src/runtime/provider-fallback.ts +55 -0
- package/src/runtime/provider-optimizer-wiring.ts +55 -0
- package/src/runtime/release-artifacts.ts +19 -103
- package/src/runtime/resume-notice.ts +1 -1
- package/src/runtime/services.ts +81 -82
- package/src/runtime/session-continuity-hints.ts +38 -0
- package/src/runtime/surface-feature-flags.ts +40 -9
- package/src/runtime/terminal-output-guard.ts +2 -2
- package/src/runtime/trust/workspace-registration.ts +10 -2
- package/src/runtime/trust/workspace-trust.ts +58 -31
- package/src/runtime/ui-services.ts +3 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +102 -6
- package/src/shell/ui-openers.ts +38 -6
- package/src/utils/format-duration.ts +1 -1
- package/src/verification/verification-ledger.ts +21 -4
- package/src/version.ts +1 -1
- package/src/input/commands/flags-runtime.ts +0 -328
|
@@ -6,6 +6,7 @@ import { ConfigManager, getConfiguredSystemPrompt } from '../config/index.ts';
|
|
|
6
6
|
import { getProviderIdFromModel } from '../config/provider-model.ts';
|
|
7
7
|
import { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
8
8
|
import { registerAllTools } from '@pellux/goodvibes-sdk/platform/tools';
|
|
9
|
+
import { createSandboxContainmentNotice } from './daemon-attach-notices.ts';
|
|
9
10
|
import { PermissionManager, createPermissionConfigReader } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
10
11
|
import { Notifier } from '@pellux/goodvibes-sdk/platform/integrations';
|
|
11
12
|
import { Compositor } from '../renderer/compositor.ts';
|
|
@@ -17,7 +18,7 @@ import type { ControlPlaneRecentEvent } from '@pellux/goodvibes-sdk/platform/con
|
|
|
17
18
|
import type { BootstrapOptions } from './context.ts';
|
|
18
19
|
import { createRuntimeStore, createDomainDispatch, type RuntimeStore } from './store/index.ts';
|
|
19
20
|
import {
|
|
20
|
-
type MutableRuntimeState, type SessionEvent, createFeatureFlagManager, RuntimeEventBus, ForensicsCollector,
|
|
21
|
+
type MutableRuntimeState, type SessionEvent, bindFeatureSettingsBridge, createFeatureFlagManager, deriveFeatureStates, RuntimeEventBus, ForensicsCollector,
|
|
21
22
|
ForensicsRegistry, generateUserSessionId, loadBootstrapSystemPrompt, syncConfiguredServices,
|
|
22
23
|
registerBootstrapHookBridge, registerBootstrapRuntimeEvents,
|
|
23
24
|
} from '@/runtime/index.ts';
|
|
@@ -25,7 +26,7 @@ import { readExecEnvScrubAllowlist } from '../input/exec-env-scrub-config.ts';
|
|
|
25
26
|
import { createSandboxExecAsk, sandboxExecAskDepsFromRuntime } from '../permissions/sandbox-exec-gate.ts';
|
|
26
27
|
import { createRuntimeServices, type RuntimeServices } from './services.ts';
|
|
27
28
|
import { runBootMemoryFold } from './memory-fold.ts';
|
|
28
|
-
import {
|
|
29
|
+
import { wireCostPricing } from '../export/cost-utils.ts';
|
|
29
30
|
import { createUiRuntimeServices, type UiRuntimeServices } from './ui-services.ts';
|
|
30
31
|
import { join } from 'node:path';
|
|
31
32
|
import { installWrfcAgentToolGuard } from '../tools/wrfc-agent-guard.ts';
|
|
@@ -33,7 +34,7 @@ import { createWrfcPersistence, type WrfcPersistence } from './wrfc-persistence.
|
|
|
33
34
|
import type { SystemMessagePriority } from '../core/system-message-router.ts';
|
|
34
35
|
import { SessionSpineClient, SessionUnionCache, TUI_SPINE_PARTICIPANT } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
35
36
|
import { SessionInboundInputPoller, createBootstrapInboundInputPoller } from './session-inbound-inputs.ts';
|
|
36
|
-
import {
|
|
37
|
+
import { trustGatedAsk, type WorkspaceTrustLevel } from './trust/workspace-trust.ts';
|
|
37
38
|
import { syncNotifierQueueIntegrations } from './bootstrap-notifier-sync.ts';
|
|
38
39
|
|
|
39
40
|
// ---------------------------------------------------------------------------
|
|
@@ -105,6 +106,7 @@ export interface BootstrapCoreState {
|
|
|
105
106
|
readonly runtimeUnsubs: Array<() => void>;
|
|
106
107
|
readonly agentStatusIntervalRef: { value: ReturnType<typeof setInterval> | null };
|
|
107
108
|
readonly permissionPromptRef: { requestPermission: PermissionRequestHandler };
|
|
109
|
+
readonly trustPromptRef: { requestTrustDecision: () => Promise<WorkspaceTrustLevel> }; // trust-at-consequence-time bridge, patched with the real modal-driving impl once the UI layer exists
|
|
108
110
|
readonly systemMessageRouterRef: { value: SystemMessageRouter | null };
|
|
109
111
|
readonly conversationFollowUpRef: { value: ((item: ConversationFollowUpItem) => void) | null };
|
|
110
112
|
/**
|
|
@@ -231,10 +233,12 @@ export async function initializeBootstrapCore(
|
|
|
231
233
|
const homeDirectory = options.homeDirectory;
|
|
232
234
|
const configManager = options.configManager;
|
|
233
235
|
|
|
236
|
+
// Gate states derive from the domain settings keys; the bridge keeps live
|
|
237
|
+
// config writes flowing (honest pending-restart markers for startup-gated
|
|
238
|
+
// features). Mirrors the SDK composition root's owned-manager wiring.
|
|
234
239
|
const featureFlags = createFeatureFlagManager();
|
|
235
|
-
featureFlags.loadFromConfig({
|
|
236
|
-
|
|
237
|
-
});
|
|
240
|
+
featureFlags.loadFromConfig({ flags: deriveFeatureStates(configManager) });
|
|
241
|
+
bindFeatureSettingsBridge(configManager, featureFlags);
|
|
238
242
|
|
|
239
243
|
const userSessionId = `user-${generateUserSessionId()}`;
|
|
240
244
|
const runtimeBus = new RuntimeEventBus();
|
|
@@ -247,20 +251,16 @@ export async function initializeBootstrapCore(
|
|
|
247
251
|
runtimeBus,
|
|
248
252
|
runtimeStore: store,
|
|
249
253
|
getConversationTitle: () => getConversationTitle(),
|
|
250
|
-
workingDir,
|
|
251
|
-
homeDirectory,
|
|
252
|
-
// Sampled BEFORE any service writes to <cwd>/.goodvibes so a new workspace isn't misread as used.
|
|
253
|
-
workspaceHadPriorState: detectPriorWorkspaceState(workingDir),
|
|
254
|
+
workingDir, homeDirectory,
|
|
254
255
|
});
|
|
255
|
-
await services.workspaceTrustManager.load(); // settle trust
|
|
256
|
+
await services.workspaceTrustManager.load(); // settle any already-persisted trust decision before any tool runs
|
|
256
257
|
const providerRegistry = services.providerRegistry;
|
|
257
258
|
providerRegistry.initModelLimits();
|
|
258
259
|
services.benchmarkStore.initBenchmarks();
|
|
259
260
|
providerRegistry.initCatalog();
|
|
260
|
-
// Wire cost-utils to the live catalog
|
|
261
|
-
// pricing from unpriced (
|
|
262
|
-
|
|
263
|
-
setPricingSource(() => providerRegistry.getRawCatalogModels());
|
|
261
|
+
// Wire cost-utils to the live catalog AND the ONE pricing resolver so every
|
|
262
|
+
// cost surface distinguishes real pricing from unpriced and names its source ("your price" vs "catalog price, as of <date>") — see cost-utils.
|
|
263
|
+
wireCostPricing(providerRegistry);
|
|
264
264
|
services.keybindingsManager.loadFromDisk();
|
|
265
265
|
domainDispatch.syncControlPlaneState({
|
|
266
266
|
enabled: Boolean(configManager.get('controlPlane.enabled')),
|
|
@@ -351,6 +351,8 @@ export async function initializeBootstrapCore(
|
|
|
351
351
|
const toolRegistry = new ToolRegistry();
|
|
352
352
|
const { fileCache, projectIndex } = registerAllTools(toolRegistry, {
|
|
353
353
|
surfaceRoot: 'tui',
|
|
354
|
+
localhostFetchApproval: services.localhostFetchApproval, // loopback-fetch ask, built once in the services composition
|
|
355
|
+
execPromptAnswerHandler: services.execPromptAnswerHandler, // terminal prompt-answer path, built once in the services composition
|
|
354
356
|
fileUndoManager: services.fileUndoManager,
|
|
355
357
|
modeManager: services.modeManager,
|
|
356
358
|
processManager: services.processManager,
|
|
@@ -373,21 +375,19 @@ export async function initializeBootstrapCore(
|
|
|
373
375
|
serviceRegistry: services.serviceRegistry,
|
|
374
376
|
overflowHandler: services.overflowHandler,
|
|
375
377
|
changeTracker: services.sessionChangeTracker,
|
|
376
|
-
//
|
|
377
|
-
// variable NAMES kept even though they look credential-bearing. See
|
|
378
|
-
// exec-env-scrub-config.ts (permissions.execEnvScrubAllowlist).
|
|
378
|
+
// Widens the allowlist of credential-looking variable NAMES kept (master switch stays on). See exec-env-scrub-config.ts.
|
|
379
379
|
credentialEnvScrub: { allowlist: readExecEnvScrubAllowlist(configManager) },
|
|
380
|
-
//
|
|
381
|
-
// registry against OUR holder rather than a fresh internal one, so the
|
|
382
|
-
// Orchestrator-backed source bound at bootstrap.ts is what the tool
|
|
383
|
-
// actually reads from. See runtime/context-accounting-source.ts.
|
|
380
|
+
// Register context_accounting against OUR holder (the Orchestrator-backed source bound at bootstrap.ts). See runtime/context-accounting-source.ts.
|
|
384
381
|
contextAccountingHolder: services.contextAccountingHolder,
|
|
382
|
+
// First contained (sandboxed) command run announces "commands now run contained" once — recorded and surfaced now.
|
|
383
|
+
onSandboxedRun: createSandboxContainmentNotice({ configManager, notify: (text) => conversation.log(`[Sandbox] ${text}`, { fg: '135' }) }),
|
|
385
384
|
});
|
|
386
|
-
// Note: installWrfcAgentToolGuard is called after routeOrBuffer is defined
|
|
387
|
-
// (further below) so the onTrace callback can route guard decisions through
|
|
388
|
-
// the pre-router buffer.
|
|
385
|
+
// Note: installWrfcAgentToolGuard is called after routeOrBuffer is defined (further below) so the onTrace callback routes guard decisions through the pre-router buffer.
|
|
389
386
|
services.agentOrchestrator.setDependencies({
|
|
390
387
|
surfaceRoot: 'tui',
|
|
388
|
+
// setDependencies is a wholesale replace: re-install the prompt-answer handler and the loopback-fetch ask here or this rewire silently drops them.
|
|
389
|
+
execPromptAnswerHandler: services.execPromptAnswerHandler,
|
|
390
|
+
localhostFetchApproval: services.localhostFetchApproval,
|
|
391
391
|
fileCache,
|
|
392
392
|
projectIndex,
|
|
393
393
|
workingDirectory: services.workingDirectory,
|
|
@@ -424,7 +424,7 @@ export async function initializeBootstrapCore(
|
|
|
424
424
|
await runBootMemoryFold(memoryStore, services.memoryEmbeddingRegistry, workingDir, logger);
|
|
425
425
|
|
|
426
426
|
const renderRequestRef = { value: (): void => {} };
|
|
427
|
-
//
|
|
427
|
+
// Coalescing render scheduler — collapses N requestRender() calls into 1 and
|
|
428
428
|
// enforces a 16ms minimum interval to cap repaints at ~60fps. renderScheduled stays
|
|
429
429
|
// set for the ENTIRE window (until run() executes), so requestRender() calls arriving
|
|
430
430
|
// on later event-loop ticks within the same 16ms window coalesce into the one
|
|
@@ -467,6 +467,9 @@ export async function initializeBootstrapCore(
|
|
|
467
467
|
const permissionPromptRef = {
|
|
468
468
|
requestPermission: (async () => ({ approved: false, remember: false })) as PermissionRequestHandler,
|
|
469
469
|
};
|
|
470
|
+
// Trust-at-consequence-time: raised by trustGatedAsk on the first non-read
|
|
471
|
+
// request in an undecided workspace; overridden once the UI layer exists — same ref-patching pattern as permissionPromptRef above.
|
|
472
|
+
const trustPromptRef = { requestTrustDecision: (async () => 'restricted') as () => Promise<WorkspaceTrustLevel> };
|
|
470
473
|
approvalBroker.start().catch((err) => logger.warn('approval broker start failed at bootstrap', { err }));
|
|
471
474
|
sharedSessionBroker.start().catch((err) => logger.warn('shared session broker start failed at bootstrap', { err }));
|
|
472
475
|
const runtimeSessionIdRef = { value: userSessionId };
|
|
@@ -663,7 +666,7 @@ export async function initializeBootstrapCore(
|
|
|
663
666
|
|
|
664
667
|
providerRegistry.startWatching(runtimeBus);
|
|
665
668
|
|
|
666
|
-
//
|
|
669
|
+
// attach the SAME WebhookNotifier instance that `/notify add|remove|clear`
|
|
667
670
|
// (notify-runtime.ts) keeps live via ctx.services.webhookNotifier, rather than
|
|
668
671
|
// constructing a second, boot-time-only instance here. Before this fix, a
|
|
669
672
|
// webhook URL added mid-session reached long-task notifications (which read
|
|
@@ -696,8 +699,7 @@ export async function initializeBootstrapCore(
|
|
|
696
699
|
await syncConfiguredServices(domainDispatch.syncIntegration, services.serviceRegistry);
|
|
697
700
|
|
|
698
701
|
const permissionManager = new PermissionManager(
|
|
699
|
-
// Composed ask layer: the workspace trust gate (outer) wraps the sandbox-aware
|
|
700
|
-
// exec gate (inner); see sandbox-exec-gate.ts. The catastrophic block is untouched.
|
|
702
|
+
// Composed ask layer: the workspace trust gate (outer) wraps the sandbox-aware exec gate (inner); see sandbox-exec-gate.ts. The catastrophic block is untouched.
|
|
701
703
|
trustGatedAsk(
|
|
702
704
|
services.workspaceTrustManager,
|
|
703
705
|
createSandboxExecAsk(
|
|
@@ -708,11 +710,13 @@ export async function initializeBootstrapCore(
|
|
|
708
710
|
localPrompt: permissionPromptRef.requestPermission,
|
|
709
711
|
}),
|
|
710
712
|
),
|
|
713
|
+
() => trustPromptRef.requestTrustDecision(), // indirection through the ref, not bound early — main.ts patches the real impl in later
|
|
711
714
|
),
|
|
712
715
|
createPermissionConfigReader(configManager),
|
|
713
716
|
policyRuntimeState,
|
|
714
717
|
services.hookDispatcher,
|
|
715
718
|
featureFlags,
|
|
719
|
+
services.userPermissionRuleStore, // durable remembered approvals (mirrors the SDK composition); permissions.rules.* lists/deletes them
|
|
716
720
|
);
|
|
717
721
|
await hookWorkbench.loadAndApplyManagedHooks();
|
|
718
722
|
|
|
@@ -757,11 +761,7 @@ export async function initializeBootstrapCore(
|
|
|
757
761
|
}, 'bootstrap.session');
|
|
758
762
|
|
|
759
763
|
runtimeUnsubs.push(
|
|
760
|
-
...registerBootstrapHookBridge({
|
|
761
|
-
runtimeBus,
|
|
762
|
-
hookDispatcher,
|
|
763
|
-
runtime,
|
|
764
|
-
}),
|
|
764
|
+
...registerBootstrapHookBridge({ runtimeBus, hookDispatcher, runtime }),
|
|
765
765
|
);
|
|
766
766
|
|
|
767
767
|
return {
|
|
@@ -783,7 +783,7 @@ export async function initializeBootstrapCore(
|
|
|
783
783
|
bootstrapUnsubs,
|
|
784
784
|
runtimeUnsubs,
|
|
785
785
|
agentStatusIntervalRef,
|
|
786
|
-
permissionPromptRef,
|
|
786
|
+
permissionPromptRef, trustPromptRef,
|
|
787
787
|
systemMessageRouterRef,
|
|
788
788
|
conversationFollowUpRef,
|
|
789
789
|
orchestratorHandleUserInputRef,
|
|
@@ -32,7 +32,7 @@ export interface ResumeSessionOptions {
|
|
|
32
32
|
readonly sessionManager: SessionManager;
|
|
33
33
|
readonly panelManager: PanelManager;
|
|
34
34
|
readonly configManager: Pick<ConfigManager, 'get' | 'getCategory'>;
|
|
35
|
-
readonly providerRegistry: Pick<ProviderRegistry, 'get' | 'getCurrentModel' | 'getForModel' | 'require'>;
|
|
35
|
+
readonly providerRegistry: Pick<ProviderRegistry, 'get' | 'getCurrentModel' | 'getForModel' | 'require' | 'resolveModelPricing'>;
|
|
36
36
|
readonly homeDirectory: string;
|
|
37
37
|
/** See CommandSessionServices.hydrateSessionUsage (command-registry.ts). */
|
|
38
38
|
readonly hydrateSessionUsage?: () => void;
|
|
@@ -93,7 +93,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
93
93
|
completeModelSelectionSideEffect,
|
|
94
94
|
} = options;
|
|
95
95
|
|
|
96
|
-
//
|
|
96
|
+
// after any resume seam replays historical messages into `conversation`,
|
|
97
97
|
// the freshly-constructed `orchestrator` still has its zeroed default usage
|
|
98
98
|
// (SDK gap — Orchestrator.usage is never persisted/reseeded). Recompute it
|
|
99
99
|
// from the replayed history so the footer doesn't show Input: 0 post-resume.
|
|
@@ -140,11 +140,11 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
140
140
|
runtimeBus,
|
|
141
141
|
});
|
|
142
142
|
|
|
143
|
-
//
|
|
143
|
+
// initial cost-budget alert threshold (USD; 0/unset = disabled).
|
|
144
144
|
// Once the session starts, the real control surface is the CostTrackerPanel
|
|
145
145
|
// itself — the in-panel 'b' key and /cost budget <usd> both call
|
|
146
146
|
// CostTrackerPanel.setBudgetThreshold() directly on the live panel instance,
|
|
147
|
-
// which
|
|
147
|
+
// which now writes through to the behavior.budgetAlertUsd config key
|
|
148
148
|
// so the background budget-breach notifier reads the same value. The env
|
|
149
149
|
// var remains a first-run convenience only: it seeds the config key when
|
|
150
150
|
// that key has never been set, so it doesn't silently override a value the
|
|
@@ -220,7 +220,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
220
220
|
{
|
|
221
221
|
// Suppress stale WRFC replay re-notifications for chains that can no
|
|
222
222
|
// longer act — gone (killed/removed → getChain null) or terminal
|
|
223
|
-
// (passed/failed). (
|
|
223
|
+
// (passed/failed). (item 1c.)
|
|
224
224
|
isChainTerminal: (chainId) => {
|
|
225
225
|
const chain = services.wrfcController.getChain(chainId);
|
|
226
226
|
return chain === null || chain.state === 'passed' || chain.state === 'failed';
|
|
@@ -307,6 +307,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
307
307
|
adaptivePlanner: services.adaptivePlanner,
|
|
308
308
|
sessionOrchestration: services.sessionOrchestration,
|
|
309
309
|
operatorClient: directTransport.operator,
|
|
310
|
+
userPermissionRuleStore: services.userPermissionRuleStore,
|
|
310
311
|
peerClient: directTransport.peer,
|
|
311
312
|
knowledgeApi,
|
|
312
313
|
// /recall's browse/link/queue/export/import subcommands and the per-turn
|
|
@@ -345,7 +346,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
345
346
|
lastGitInfoRef.value = info;
|
|
346
347
|
requestRender();
|
|
347
348
|
}).catch(() => { /* non-fatal */ });
|
|
348
|
-
//
|
|
349
|
+
// FIX 2: the header's git segment otherwise only refreshes on
|
|
349
350
|
// TURN_COMPLETED/TOOL_SUCCEEDED/TOOL_FAILED (see turn-event-wiring.ts's
|
|
350
351
|
// refreshGit()) — if the user runs `git init` externally and never submits
|
|
351
352
|
// another turn, the header stays stuck on the startup-time fallback
|
package/src/runtime/bootstrap.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { AcpManager } from '@pellux/goodvibes-sdk/platform/acp';
|
|
|
15
15
|
import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers';
|
|
16
16
|
import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
17
17
|
import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
18
|
+
import type { WorkspaceTrustLevel } from './trust/workspace-trust.ts';
|
|
18
19
|
import type { CommandContext } from '../input/command-registry.ts';
|
|
19
20
|
import type { InputHistory } from '../input/input-history.ts';
|
|
20
21
|
import type { GitStatusProvider, GitHeaderInfo } from '../renderer/git-status.ts';
|
|
@@ -40,9 +41,9 @@ import { createBootstrapShell } from './bootstrap-shell.ts';
|
|
|
40
41
|
import { announceResumeState } from './resume-notice.ts';
|
|
41
42
|
import { announceInstallSelfCheck } from './install-self-check-startup.ts';
|
|
42
43
|
import { buildSharedOrchestratorCoreServices, refreshMemoryRecallSnapshot } from './orchestrator-core-services.ts';
|
|
44
|
+
import { consumeDaemonAttachNotices, consumeExternalDaemonAttachNotices } from './daemon-attach-notices.ts';
|
|
43
45
|
import { createContextAccountingSource } from './context-accounting-source.ts';
|
|
44
|
-
import {
|
|
45
|
-
import { createSafeHostServeFactory } from '../daemon/safe-serve.ts';
|
|
46
|
+
import { createEmbeddedServiceFactories } from './embedded-service-factories.ts';
|
|
46
47
|
import { buildRelayExternalServiceMethods } from './relay-reachability-bridge.ts';
|
|
47
48
|
import { startMcpConfigAutoReload } from '../mcp/runtime-reload.ts';
|
|
48
49
|
|
|
@@ -95,6 +96,8 @@ export type BootstrapContext = RuntimeContext & {
|
|
|
95
96
|
setRenderRequest: (fn: () => void) => void;
|
|
96
97
|
/** Shell-owned permission prompt bridge that main.ts patches after UI setup. */
|
|
97
98
|
permissionPromptRef: { requestPermission: PermissionRequestHandler };
|
|
99
|
+
/** Shell-owned trust-decision bridge that main.ts patches after UI setup (trust asks at consequence time). */
|
|
100
|
+
trustPromptRef: { requestTrustDecision: () => Promise<WorkspaceTrustLevel> };
|
|
98
101
|
/** Load the most recently saved conversation from disk. */
|
|
99
102
|
loadLastConversation: () => { messages: Array<Record<string, unknown>> } | null;
|
|
100
103
|
/** Write the last-session pointer file (used after session resume). */
|
|
@@ -172,7 +175,7 @@ export async function bootstrapRuntime(
|
|
|
172
175
|
bootstrapUnsubs,
|
|
173
176
|
runtimeUnsubs,
|
|
174
177
|
agentStatusIntervalRef,
|
|
175
|
-
permissionPromptRef,
|
|
178
|
+
permissionPromptRef, trustPromptRef,
|
|
176
179
|
systemMessageRouterRef,
|
|
177
180
|
conversationFollowUpRef,
|
|
178
181
|
orchestratorHandleUserInputRef,
|
|
@@ -192,9 +195,8 @@ export async function bootstrapRuntime(
|
|
|
192
195
|
pluginManager,
|
|
193
196
|
} = services;
|
|
194
197
|
|
|
195
|
-
// A liveness flip is only PAINTED once something calls requestRender();
|
|
196
|
-
//
|
|
197
|
-
// incidental activity redrew it (minutes, during an idle stretch).
|
|
198
|
+
// A liveness flip is only PAINTED once something calls requestRender(); without
|
|
199
|
+
// this, the footer's spine segment sat correct-but-undrawn until incidental activity redrew it (minutes, during an idle stretch).
|
|
198
200
|
sessionUnionCache.setOnTransition(() => requestRender());
|
|
199
201
|
|
|
200
202
|
// ── Phase 6: Orchestrator + AcpManager ───────────────────────────────────
|
|
@@ -237,7 +239,7 @@ export async function bootstrapRuntime(
|
|
|
237
239
|
logger.debug('companion handleUserInput safety catch', { error: String(err) });
|
|
238
240
|
});
|
|
239
241
|
};
|
|
240
|
-
// Shared payload (single source of truth, includes
|
|
242
|
+
// Shared payload (single source of truth, includes the memoryRegistry —
|
|
241
243
|
// see orchestrator-core-services.ts) plus this site's cacheHitTracker.
|
|
242
244
|
orchestrator.setCoreServices({
|
|
243
245
|
...buildSharedOrchestratorCoreServices({ services, configManager, providerRegistry }),
|
|
@@ -254,8 +256,7 @@ export async function bootstrapRuntime(
|
|
|
254
256
|
|
|
255
257
|
const acpManager = new AcpManager({
|
|
256
258
|
requestPermission: (request) => permissionPromptRef.requestPermission(request),
|
|
257
|
-
runtimeBus,
|
|
258
|
-
hookDispatcher: services.hookDispatcher,
|
|
259
|
+
runtimeBus, hookDispatcher: services.hookDispatcher,
|
|
259
260
|
});
|
|
260
261
|
const acpTaskAdapter = new AcpTaskAdapter(store);
|
|
261
262
|
const ACP_TASK_SYNC_INTERVAL_MS = 1_000;
|
|
@@ -265,7 +266,9 @@ export async function bootstrapRuntime(
|
|
|
265
266
|
bootstrapUnsubs.push(() => clearInterval(acpTaskSyncInterval));
|
|
266
267
|
orchestrator.registerDelegateTool(acpManager);
|
|
267
268
|
const opsTaskManager = createTaskManager(store, runtimeBus, userSessionId);
|
|
268
|
-
|
|
269
|
+
// Operator interventions ride the control-plane gateway capability
|
|
270
|
+
// (controlPlane.gateway, on by default; the old never-registered gate id shipped this dead).
|
|
271
|
+
const opsControlPlane = services.featureFlags.isEnabled('control-plane-gateway')
|
|
269
272
|
? new OpsControlPlane(opsTaskManager, runtimeBus, store, userSessionId)
|
|
270
273
|
: undefined;
|
|
271
274
|
|
|
@@ -300,7 +303,7 @@ export async function bootstrapRuntime(
|
|
|
300
303
|
wrfcPersistence.rehydrate();
|
|
301
304
|
const commandRegistry = shell.commandRegistry;
|
|
302
305
|
const commandContext = shell.commandContext;
|
|
303
|
-
// Boot resume notice (
|
|
306
|
+
// Boot resume notice (item 1): after rehydrate() so chain history is ready, before
|
|
304
307
|
// the operator can type anything. Fire-and-forget, same as main.ts's non-blocking
|
|
305
308
|
// `void workspaceCheckpointManager.init().catch(() => {})` — local file I/O only,
|
|
306
309
|
// resolves well before a human can react to the first rendered frame.
|
|
@@ -318,7 +321,7 @@ export async function bootstrapRuntime(
|
|
|
318
321
|
});
|
|
319
322
|
announceInstallSelfCheck(systemMessageRouter);
|
|
320
323
|
const { gitStatusProvider, inputHistory, lastGitInfoRef } = shell;
|
|
321
|
-
//
|
|
324
|
+
// FIX 2: dispose the header's live-repo-state poll (git-status.ts
|
|
322
325
|
// startPolling) on shutdown, same pattern as acpTaskSyncInterval above.
|
|
323
326
|
bootstrapUnsubs.push(() => gitStatusProvider.stopPolling());
|
|
324
327
|
const pluginCommandRegistry = {
|
|
@@ -475,21 +478,9 @@ export async function bootstrapRuntime(
|
|
|
475
478
|
]);
|
|
476
479
|
};
|
|
477
480
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
runtimeBus: bus,
|
|
482
|
-
userAuth,
|
|
483
|
-
runtimeServices,
|
|
484
|
-
serveFactory: createSafeHostServeFactory('Embedded daemon'),
|
|
485
|
-
}),
|
|
486
|
-
createHttpListener: (dispatcher, userAuth, configManager) => new HttpListener({
|
|
487
|
-
hookDispatcher: dispatcher,
|
|
488
|
-
userAuth,
|
|
489
|
-
configManager,
|
|
490
|
-
serveFactory: createSafeHostServeFactory('Embedded HTTP listener'),
|
|
491
|
-
}),
|
|
492
|
-
});
|
|
481
|
+
// Embedded DaemonServer/HttpListener factories (incl. the facade auto-update
|
|
482
|
+
// suppression) — see embedded-service-factories.ts.
|
|
483
|
+
const createExternalServiceFactories = createEmbeddedServiceFactories;
|
|
493
484
|
|
|
494
485
|
let externalServices: ExternalServicesHandle = {
|
|
495
486
|
daemonServer: null, httpListener: null,
|
|
@@ -505,6 +496,9 @@ export async function bootstrapRuntime(
|
|
|
505
496
|
platformExternalServices.externalServices = {
|
|
506
497
|
...buildRelayExternalServiceMethods(() => externalServices.daemonServer),
|
|
507
498
|
inspect: inspectExternalServices,
|
|
499
|
+
// Undelivered daemon receipts (update/crash/migration) for the attach-time
|
|
500
|
+
// consuming read; empty when no daemon runs. Marked delivered once served.
|
|
501
|
+
collectDaemonReceipts: () => (externalServices.daemonServer as { collectDaemonReceipts?: () => readonly { id: string; text: string; at: number }[] } | null)?.collectDaemonReceipts?.() ?? [],
|
|
508
502
|
restart: async () => {
|
|
509
503
|
if (externalServicesPromise) {
|
|
510
504
|
try {
|
|
@@ -527,10 +521,19 @@ export async function bootstrapRuntime(
|
|
|
527
521
|
externalServices = await externalServicesPromise;
|
|
528
522
|
controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
|
|
529
523
|
syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
|
|
524
|
+
await renderDaemonAttachNotices(companionTokenRecord.token);
|
|
530
525
|
requestRender();
|
|
531
526
|
return inspectExternalServices();
|
|
532
527
|
},
|
|
533
528
|
};
|
|
529
|
+
// The attach-time consuming read, exactly once as notices. An adopted EXTERNAL daemon has no in-process handle (daemonServer null), so read its own /status?receipts=consume over HTTP; the embedded daemon keeps the in-process fold.
|
|
530
|
+
const renderDaemonAttachNotices = async (daemonToken: string): Promise<void> => {
|
|
531
|
+
const daemonStatus = externalServices.daemonStatus;
|
|
532
|
+
const notices = daemonStatus.mode === 'external' && daemonStatus.baseUrl
|
|
533
|
+
? await consumeExternalDaemonAttachNotices({ baseUrl: daemonStatus.baseUrl, authToken: daemonToken })
|
|
534
|
+
: consumeDaemonAttachNotices({ configManager, collectReceipts: () => platformExternalServices.externalServices.collectDaemonReceipts() });
|
|
535
|
+
for (const notice of notices) systemMessageRouter.high(`[Daemon] ${notice}`);
|
|
536
|
+
};
|
|
534
537
|
deferredStartup.schedule({
|
|
535
538
|
label: 'plugins',
|
|
536
539
|
run: async () => {
|
|
@@ -591,6 +594,7 @@ export async function bootstrapRuntime(
|
|
|
591
594
|
externalServices = await externalServicesPromise;
|
|
592
595
|
controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
|
|
593
596
|
syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
|
|
597
|
+
await renderDaemonAttachNotices(companionTokenRecord.token);
|
|
594
598
|
requestRender();
|
|
595
599
|
},
|
|
596
600
|
onError: (error) => {
|
|
@@ -713,7 +717,7 @@ export async function bootstrapRuntime(
|
|
|
713
717
|
agentStatusIntervalRef,
|
|
714
718
|
orchestratorRefs,
|
|
715
719
|
setRenderRequest,
|
|
716
|
-
permissionPromptRef,
|
|
720
|
+
permissionPromptRef, trustPromptRef,
|
|
717
721
|
loadLastConversation: () => loadLastConversation({
|
|
718
722
|
workingDirectory: services.workingDirectory,
|
|
719
723
|
homeDirectory: services.homeDirectory,
|
|
@@ -774,10 +778,8 @@ export async function bootstrapRuntime(
|
|
|
774
778
|
},
|
|
775
779
|
};
|
|
776
780
|
|
|
777
|
-
// ── Phase 12b: Operator Control Plane wiring (
|
|
778
|
-
//
|
|
779
|
-
// The store and task manager are created unconditionally so they reflect the
|
|
780
|
-
// real runtime state (tasks registered before the flag check are visible).
|
|
781
|
+
// ── Phase 12b: Operator Control Plane wiring (capability-gated); store and
|
|
782
|
+
// task manager exist unconditionally so pre-gate tasks stay visible. ───────
|
|
781
783
|
ctx.commandContext.ops.acpManager = acpManager;
|
|
782
784
|
if (opsControlPlane) {
|
|
783
785
|
ctx.commandContext.openOpsPanel = () => {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* channel-composition — route bindings, surface registry, and channel plugin
|
|
3
|
+
* registry for the TUI composition root (split out of services.ts for the
|
|
4
|
+
* line cap).
|
|
5
|
+
*
|
|
6
|
+
* RECORDED DIVERGENCE from the SDK composition root: SurfaceRegistry and
|
|
7
|
+
* ChannelPluginRegistry are constructed WITHOUT the gate manager. The SDK's
|
|
8
|
+
* surface gate map only names six surfaces (web/slack/discord/ntfy/webhook/
|
|
9
|
+
* homeassistant) and reads every other adapter (telegram, whatsapp, signal,
|
|
10
|
+
* imessage, msteams, ...) as OFF whenever a manager is present — no settings
|
|
11
|
+
* key could re-enable them. Until the gate map covers every adapter, the
|
|
12
|
+
* honest governing switch here stays the surfaces.<id>.enabled config each
|
|
13
|
+
* adapter already reads, exactly as the feature surface's own
|
|
14
|
+
* constant-binding rule states. RouteBindingManager keeps its manager — its
|
|
15
|
+
* route-binding gate is a real registry feature.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { AutomationRouteStore } from '@pellux/goodvibes-sdk/platform/automation';
|
|
19
|
+
import { ChannelPluginRegistry, RouteBindingManager, SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
|
|
20
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
21
|
+
import type { FeatureFlagManager, RuntimeEventBus } from '@/runtime/index.ts';
|
|
22
|
+
import type { RuntimeStore } from './store/index.ts';
|
|
23
|
+
|
|
24
|
+
export interface ChannelComposition {
|
|
25
|
+
readonly routeBindings: RouteBindingManager;
|
|
26
|
+
readonly surfaceRegistry: SurfaceRegistry;
|
|
27
|
+
readonly channelPlugins: ChannelPluginRegistry;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createChannelComposition(options: {
|
|
31
|
+
readonly configManager: ConfigManager;
|
|
32
|
+
readonly runtimeStore: RuntimeStore;
|
|
33
|
+
readonly runtimeBus: RuntimeEventBus;
|
|
34
|
+
readonly featureFlags: FeatureFlagManager;
|
|
35
|
+
}): ChannelComposition {
|
|
36
|
+
const routeBindings = new RouteBindingManager({
|
|
37
|
+
store: new AutomationRouteStore({ configManager: options.configManager }),
|
|
38
|
+
runtimeStore: options.runtimeStore,
|
|
39
|
+
runtimeBus: options.runtimeBus,
|
|
40
|
+
featureFlags: options.featureFlags,
|
|
41
|
+
});
|
|
42
|
+
const surfaceRegistry = new SurfaceRegistry(options.configManager, options.runtimeStore);
|
|
43
|
+
const channelPlugins = new ChannelPluginRegistry();
|
|
44
|
+
surfaceRegistry.attachPluginRegistry(channelPlugins);
|
|
45
|
+
return { routeBindings, surfaceRegistry, channelPlugins };
|
|
46
|
+
}
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// code-index-services.ts — repo source-tree code index (TUI wiring)
|
|
3
3
|
//
|
|
4
4
|
// Constructs the TUI's repo source-tree code index: CodeIndexStore
|
|
5
|
-
// (@pellux/goodvibes-sdk/platform/state
|
|
6
|
-
// Stage A). Extracted into its own module rather than built inline in
|
|
5
|
+
// (@pellux/goodvibes-sdk/platform/state). Extracted into its own module rather than built inline in
|
|
7
6
|
// services.ts: services.ts sits at the architecture check's 800-line cap
|
|
8
7
|
// (scripts/check-architecture.ts), so any new service gets its own
|
|
9
8
|
// construction module and a single wiring call there (mirrors
|
|
@@ -28,7 +27,7 @@
|
|
|
28
27
|
// (interactive main.ts, the daemon, and every test fixture) shares one
|
|
29
28
|
// honest, user-visible on/off switch rather than needing to thread a new
|
|
30
29
|
// boolean through every call site. With it off (the default), `/codebase
|
|
31
|
-
// build` is the explicit, visible trigger — exactly the shape
|
|
30
|
+
// build` is the explicit, visible trigger — exactly the shape this design
|
|
32
31
|
// asks for.
|
|
33
32
|
//
|
|
34
33
|
// Shares memoryEmbeddingRegistry with MemoryStore (constructed in
|
|
@@ -68,7 +67,7 @@ export interface CodeIndexServicesDeps {
|
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
/**
|
|
71
|
-
* Whether
|
|
70
|
+
* Whether code auto-injection / tool-site reindex may run: the SAME
|
|
72
71
|
* storage.codeIndexEnabled switch that gates auto-build. Read live so a runtime
|
|
73
72
|
* /config toggle takes effect without restart. (The separate, default-off
|
|
74
73
|
* `agent-passive-code-injection` feature flag gates injection additionally; this
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* daemon-attach-notices — the attach-time consuming read.
|
|
3
|
+
*
|
|
4
|
+
* On daemon attach a surface reads the daemon's undelivered receipts
|
|
5
|
+
* (update/crash/migration) AND the pending feature-announcement queue
|
|
6
|
+
* (web-surface URL, the sandbox first-contained-run line, …) exactly once,
|
|
7
|
+
* rendering each as a one-line notice. Both sources clear on read — the
|
|
8
|
+
* facade's receipt queue marks its entries delivered, and drainPending() clears
|
|
9
|
+
* the announcement queue — so a second attach with nothing new shows nothing.
|
|
10
|
+
* This mirrors the daemon's own /status?receipts=consume fold, done in-process
|
|
11
|
+
* for the embedded daemon.
|
|
12
|
+
*/
|
|
13
|
+
import { FeatureAnnouncementStore, featureAnnouncementsPath, createSandboxContainmentAnnouncer } from '@pellux/goodvibes-sdk/platform/runtime/feature-announcements';
|
|
14
|
+
import type { ConfigManager } from '../config/index.ts';
|
|
15
|
+
|
|
16
|
+
/** A daemon receipt line (already a human one-liner, e.g. "restarted after a crash at 14:32"). */
|
|
17
|
+
export interface DaemonReceiptLike {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly text: string;
|
|
20
|
+
readonly at: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The minimal announcement-store surface this consume drains. */
|
|
24
|
+
export interface DrainableAnnouncementStore {
|
|
25
|
+
drainPending(): readonly { readonly id: string; readonly text: string; readonly at: number }[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DaemonAttachNoticesDeps {
|
|
29
|
+
readonly configManager: Pick<ConfigManager, 'getControlPlaneConfigDir'>;
|
|
30
|
+
/** Reads (and marks delivered) the daemon's undelivered receipts. */
|
|
31
|
+
readonly collectReceipts: () => readonly DaemonReceiptLike[];
|
|
32
|
+
/** Injectable announcement store; defaults to the shared control-plane file store. */
|
|
33
|
+
readonly announcementStore?: DrainableAnnouncementStore;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Consume — exactly once — the daemon's receipts and the pending announcement
|
|
38
|
+
* queue, returning the one-line notices to render. Receipts first (crash /
|
|
39
|
+
* update / migration), then announcements (web-surface URL, sandbox line, …).
|
|
40
|
+
*/
|
|
41
|
+
export function consumeDaemonAttachNotices(deps: DaemonAttachNoticesDeps): string[] {
|
|
42
|
+
const store = deps.announcementStore
|
|
43
|
+
?? new FeatureAnnouncementStore(featureAnnouncementsPath(deps.configManager));
|
|
44
|
+
const notices: string[] = [];
|
|
45
|
+
for (const receipt of deps.collectReceipts()) {
|
|
46
|
+
if (receipt.text.trim().length > 0) notices.push(receipt.text);
|
|
47
|
+
}
|
|
48
|
+
for (const announcement of store.drainPending()) {
|
|
49
|
+
if (announcement.text.trim().length > 0) notices.push(announcement.text);
|
|
50
|
+
}
|
|
51
|
+
return notices;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Milliseconds an external-daemon receipts read waits before giving up. */
|
|
55
|
+
const EXTERNAL_RECEIPTS_TIMEOUT_MS = 1500;
|
|
56
|
+
|
|
57
|
+
export interface ExternalDaemonAttachNoticesDeps {
|
|
58
|
+
/** The adopted daemon's base URL, e.g. "http://127.0.0.1:3421". */
|
|
59
|
+
readonly baseUrl: string;
|
|
60
|
+
/** The shared bearer the adopted daemon authenticates (the companion-pairing token). */
|
|
61
|
+
readonly authToken: string;
|
|
62
|
+
/** Injectable fetch for tests; defaults to the global fetch. */
|
|
63
|
+
readonly fetchImpl?: typeof fetch;
|
|
64
|
+
/** Injectable timeout (ms); defaults to EXTERNAL_RECEIPTS_TIMEOUT_MS. */
|
|
65
|
+
readonly timeoutMs?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Consume — exactly once — a separately-hosted (external/systemd) daemon's
|
|
70
|
+
* undelivered receipts AND drained announcements over HTTP, returning the
|
|
71
|
+
* one-line notices to render. The default deployment adopts such a daemon with
|
|
72
|
+
* no in-process handle (daemonServer is null), so the embedded in-process fold
|
|
73
|
+
* sees nothing; this hits the daemon's own consuming endpoint instead —
|
|
74
|
+
* GET <baseUrl>/status?receipts=consume with the shared bearer. The daemon
|
|
75
|
+
* marks the served receipts delivered as it responds (its own exactly-once
|
|
76
|
+
* store), so a second attach with nothing new returns []. Any transport, auth,
|
|
77
|
+
* or parse failure yields no notices rather than throwing into the attach path.
|
|
78
|
+
*/
|
|
79
|
+
export async function consumeExternalDaemonAttachNotices(
|
|
80
|
+
deps: ExternalDaemonAttachNoticesDeps,
|
|
81
|
+
): Promise<string[]> {
|
|
82
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
83
|
+
const controller = new AbortController();
|
|
84
|
+
const timer = setTimeout(() => controller.abort(), deps.timeoutMs ?? EXTERNAL_RECEIPTS_TIMEOUT_MS);
|
|
85
|
+
try {
|
|
86
|
+
const headers = new Headers();
|
|
87
|
+
if (deps.authToken.trim()) headers.set('Authorization', `Bearer ${deps.authToken.trim()}`);
|
|
88
|
+
const url = `${deps.baseUrl.replace(/\/+$/, '')}/status?receipts=consume`;
|
|
89
|
+
const response = await fetchImpl(url, { headers, signal: controller.signal });
|
|
90
|
+
if (!response.ok) return [];
|
|
91
|
+
const body = (await response.json()) as { receipts?: unknown } | null;
|
|
92
|
+
const receipts = Array.isArray(body?.receipts) ? body.receipts : [];
|
|
93
|
+
const notices: string[] = [];
|
|
94
|
+
for (const entry of receipts) {
|
|
95
|
+
if (entry && typeof (entry as { text?: unknown }).text === 'string') {
|
|
96
|
+
const text = (entry as { text: string }).text;
|
|
97
|
+
if (text.trim().length > 0) notices.push(text);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return notices;
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
} finally {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The "commands now run contained" first-run announcer for the sandboxed exec
|
|
110
|
+
* path. Returns the callback registerAllTools calls on each contained run: the
|
|
111
|
+
* FIRST call records the announce-once line (so any later attach-time consume
|
|
112
|
+
* also sees it) and surfaces it now via `notify`; every later run is silent.
|
|
113
|
+
*/
|
|
114
|
+
export function createSandboxContainmentNotice(deps: {
|
|
115
|
+
readonly configManager: Pick<ConfigManager, 'getControlPlaneConfigDir'>;
|
|
116
|
+
readonly notify: (text: string) => void;
|
|
117
|
+
}): () => void {
|
|
118
|
+
const store = new FeatureAnnouncementStore(featureAnnouncementsPath(deps.configManager));
|
|
119
|
+
return createSandboxContainmentAnnouncer(store, (announcement) => deps.notify(announcement.text));
|
|
120
|
+
}
|