@pellux/goodvibes-tui 1.23.0 → 1.24.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 +14 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +3 -3
- package/src/cli/bundle-command.ts +4 -0
- package/src/cli/help.ts +5 -0
- package/src/cli/management-utils.ts +4 -0
- package/src/cluster/commands.ts +382 -0
- package/src/cluster/remote-daemon-target.ts +225 -0
- package/src/cluster/render.ts +268 -0
- package/src/core/conversation-line-cache.ts +11 -6
- package/src/core/conversation-render-context.ts +122 -0
- package/src/core/conversation-rendering.ts +86 -89
- package/src/daemon/cli.ts +71 -2
- package/src/daemon/handlers/inbox/index.ts +63 -3
- package/src/daemon/handlers/inbox/poller.ts +54 -8
- package/src/input/command-args-hint.ts +2 -0
- package/src/input/commands/cluster-runtime.ts +53 -0
- package/src/input/commands.ts +2 -0
- package/src/input/settings-modal-data.ts +6 -0
- package/src/main.ts +3 -0
- package/src/renderer/conversation-surface.ts +23 -6
- package/src/renderer/conversation-tree.ts +142 -93
- package/src/renderer/tool-call.ts +13 -12
- package/src/runtime/bootstrap.ts +6 -1
- package/src/runtime/cluster-composition.ts +124 -0
- package/src/runtime/cluster-group-composition.ts +284 -0
- package/src/runtime/daemon-handler-composition.ts +80 -0
- package/src/runtime/disposal-wiring.ts +66 -0
- package/src/runtime/durability-services.ts +7 -3
- package/src/runtime/embedded-service-factories.ts +19 -1
- package/src/runtime/process-lifecycle.ts +9 -1
- package/src/runtime/remote-execution-composition.ts +62 -0
- package/src/runtime/services.ts +48 -48
- package/src/verification/verification-ledger.ts +38 -0
- package/src/version.ts +1 -1
package/src/runtime/services.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { StepUpService } from '@pellux/goodvibes-sdk/daemon';
|
|
|
11
11
|
import { PairingTokenManager } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
12
12
|
import { resolvePairingWebOrigin } from '../core/pairing-origin.ts';
|
|
13
13
|
import { attachWsOnlyGatewayVerbHandlers } from '@pellux/goodvibes-terminal-shell';
|
|
14
|
+
import { createDisposalScope, registerSurfaceRuntimePollers } from './disposal-wiring.ts';
|
|
14
15
|
import { WatcherRegistry } from '@pellux/goodvibes-sdk/platform/watchers';
|
|
15
16
|
import { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
16
17
|
import { createWebKnowledgeGapRepairer } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
@@ -36,8 +37,7 @@ import type { DomainDispatch, RuntimeStore } from './store/index.ts';
|
|
|
36
37
|
import {
|
|
37
38
|
type RuntimeEventBus, DistributedRuntimeManager, RemoteRunnerRegistry, RemoteSupervisor, IntegrationHelperService,
|
|
38
39
|
IdempotencyStore, ComponentHealthMonitor, WorktreeRegistry, SandboxSessionRegistry, createShellPathService,
|
|
39
|
-
type ShellPathService, type FeatureFlagManager, createFeatureFlagManager, PolicyRuntimeState,
|
|
40
|
-
type SessionSurface,
|
|
40
|
+
type ShellPathService, type FeatureFlagManager, createFeatureFlagManager, PolicyRuntimeState, type SessionSurface,
|
|
41
41
|
} from '@/runtime/index.ts';
|
|
42
42
|
import { createSessionStorageServices } from './session-storage-services.ts';
|
|
43
43
|
import { VoiceProviderRegistry, VoiceService, ensureBuiltinVoiceProviders } from '@pellux/goodvibes-sdk/platform/voice';
|
|
@@ -55,6 +55,7 @@ import { SessionManager, CrossSessionTaskRegistry, SessionChangeTracker } from '
|
|
|
55
55
|
import { ApiTokenAuditor, UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
|
|
56
56
|
import { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
|
|
57
57
|
import { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
58
|
+
import { createRemoteExecutionServices } from './remote-execution-composition.ts';
|
|
58
59
|
import { BenchmarkStore, CacheHitTracker, FavoritesStore, ModelLimitsService, ProviderCapabilityRegistry, ProviderOptimizer, ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
59
60
|
import { KeybindingsManager } from '../input/keybindings.ts';
|
|
60
61
|
import { AdaptivePlanner, DeterministicReplayEngine, ExecutionPlanManager, SessionLineageTracker, SessionMemoryStore } from '@pellux/goodvibes-sdk/platform/core';
|
|
@@ -67,16 +68,10 @@ import { createWorkstreamServices, type OrchestrationEngine, type WorkstreamComm
|
|
|
67
68
|
import { wireFleetNeedsInputPush } from './fleet-needs-input-push.ts';
|
|
68
69
|
import { codeIndexDbPath, createCodeIndexServices, createStoreRerooter, isCodeInjectionSettingEnabled } from './code-index-services.ts';
|
|
69
70
|
import type { WorkPlanStore } from '../work-plans/work-plan-store.ts';
|
|
70
|
-
import {
|
|
71
|
-
import
|
|
72
|
-
import {
|
|
73
|
-
import {
|
|
74
|
-
import { registerInboxMethods } from '../daemon/handlers/inbox/index.ts';
|
|
75
|
-
import { registerTriagedInbox } from '../daemon/handlers/triage/index.ts';
|
|
76
|
-
import { registerDraftMethods } from '../daemon/handlers/drafts/index.ts';
|
|
77
|
-
import { registerCalendar } from '../daemon/handlers/calendar/index.ts';
|
|
78
|
-
import { registerEmailMethods } from '../daemon/handlers/email/index.ts';
|
|
79
|
-
import { registerRemoteSurface } from '../daemon/handlers/remote/index.ts';
|
|
71
|
+
import type { DaemonHandlerSurfaces } from '../daemon/handlers/index.ts';
|
|
72
|
+
import { createDaemonHandlerComposition } from './daemon-handler-composition.ts';
|
|
73
|
+
import { createClusterServices, startClusterServices, type ClusterGroupComposition } from './cluster-group-composition.ts';
|
|
74
|
+
import type { ClusterCoordinator } from '@pellux/goodvibes-sdk/platform/cluster';
|
|
80
75
|
import { WorkspaceTrustManager } from './trust/workspace-trust.ts';
|
|
81
76
|
import { ensureConfiguredModelIsRoutable } from './provider-fallback.ts';
|
|
82
77
|
|
|
@@ -184,6 +179,12 @@ export interface RuntimeServices {
|
|
|
184
179
|
readonly toolLLM: ToolLLM;
|
|
185
180
|
readonly distributedRuntime: DistributedRuntimeManager;
|
|
186
181
|
readonly daemonHandlers: DaemonHandlerSurfaces;
|
|
182
|
+
/** Elects the one node on this network that consumes inbound messages; hand it to the DaemonServer so its consumers share this leadership instead of holding a second election. */
|
|
183
|
+
readonly clusterCoordinator: ClusterCoordinator;
|
|
184
|
+
/** LAN group membership: identity, keys, roster, and the `cluster` verbs. */
|
|
185
|
+
readonly clusterGroup: ClusterGroupComposition;
|
|
186
|
+
/** Start the group layer and then the election, in that order. Idempotent. */
|
|
187
|
+
readonly startCluster: () => Promise<void>;
|
|
187
188
|
readonly remoteRunnerRegistry: RemoteRunnerRegistry;
|
|
188
189
|
readonly remoteSupervisor: RemoteSupervisor;
|
|
189
190
|
readonly sessionMemoryStore: SessionMemoryStore;
|
|
@@ -231,10 +232,11 @@ export interface RuntimeServices {
|
|
|
231
232
|
readonly integrationHelpers: IntegrationHelperService;
|
|
232
233
|
/** Re-root path-bound stores (MemoryStore, ProjectIndex) to a new working directory, called by WorkspaceSwapManager after verification; stores needing a process restart just warn-log and keep serving the old path until the daemon restarts with the new --working-dir. */
|
|
233
234
|
rerootStores(newWorkingDir: string): Promise<void>;
|
|
235
|
+
dispose(): void; // Stop every poller this graph started; best-effort, total, idempotent. This surface owns its graph — see disposal-wiring.ts.
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeServices {
|
|
237
|
-
const workingDirectory = options.workingDir;
|
|
239
|
+
const disposalScope = createDisposalScope('RuntimeServices'); const workingDirectory = options.workingDir; // disposal seam: see ./disposal-wiring.ts
|
|
238
240
|
const homeDirectory = options.homeDirectory;
|
|
239
241
|
const shellPaths = createShellPathService({
|
|
240
242
|
workingDirectory,
|
|
@@ -419,6 +421,12 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
419
421
|
const memorySpine = new MemorySpineClient({ local: createLocalMemoryAccess(memoryRegistry) });
|
|
420
422
|
const deliveryManager = new AutomationDeliveryManager({
|
|
421
423
|
configManager,
|
|
424
|
+
// This manager builds the delivery router the daemon actually replies
|
|
425
|
+
// through (bootstrap.ts hands it to the daemon facade). Without the
|
|
426
|
+
// secrets manager it cannot resolve a goodvibes://secrets/... credential,
|
|
427
|
+
// so Telegram accepted every inbound message and dropped every reply with
|
|
428
|
+
// "Missing Telegram bot token" while ntfy — which needs no secret — worked.
|
|
429
|
+
secretsManager,
|
|
422
430
|
serviceRegistry,
|
|
423
431
|
runtimeBus: options.runtimeBus,
|
|
424
432
|
runtimeStore: options.runtimeStore,
|
|
@@ -498,43 +506,30 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
498
506
|
automationBridge: automationManager,
|
|
499
507
|
});
|
|
500
508
|
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
credentials: createDaemonCredentialStore(secretsManager),
|
|
509
|
+
// Which machines on this network are "us", and which of them reads the
|
|
510
|
+
// shared inbox. Both inert until startCluster() — no socket, no key material
|
|
511
|
+
// read; see cluster-group-composition.ts for why they are built together.
|
|
512
|
+
const { clusterGroup, clusterCoordinator } = createClusterServices({
|
|
513
|
+
configManager, shellPaths, secretsManager,
|
|
514
|
+
});
|
|
515
|
+
// Daemon handler surfaces (see daemon-handler-composition.ts); the inbox
|
|
516
|
+
// poller registers itself with the coordinator rather than starting eagerly.
|
|
517
|
+
const daemonHandlers = createDaemonHandlerComposition({
|
|
518
|
+
gatewayMethods,
|
|
519
|
+
secretsManager,
|
|
513
520
|
configManager,
|
|
514
521
|
workingDirectory,
|
|
515
522
|
homeDirectory,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const daemonHandlers = registerDaemonHandlers(handlerContext, {
|
|
519
|
-
registerRouting,
|
|
520
|
-
registerInbox: (ctx, routing) =>
|
|
521
|
-
registerTriagedInbox(ctx, (inboxCtx) => registerInboxMethods(inboxCtx, routing))
|
|
522
|
-
.unregister,
|
|
523
|
-
registerDrafts: (ctx) => registerDraftMethods(ctx),
|
|
524
|
-
registerCalendar,
|
|
525
|
-
registerEmail: (ctx) => registerEmailMethods(ctx),
|
|
526
|
-
registerRemote: (ctx) => registerRemoteSurface(ctx, { manager: distributedRuntime }),
|
|
523
|
+
distributedRuntime,
|
|
524
|
+
clusterCoordinator,
|
|
527
525
|
});
|
|
528
526
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const sandboxSessionRegistry
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
});
|
|
536
|
-
mcpRegistry.setRuntimeBus(options.runtimeBus);
|
|
537
|
-
mcpRegistry.setSandboxRuntime(configManager, sandboxSessionRegistry);
|
|
527
|
+
// Remote runners and the sandboxes tool calls are confined to; see
|
|
528
|
+
// remote-execution-composition.ts for why the four are built as one.
|
|
529
|
+
const { remoteRunnerRegistry, remoteSupervisor, sandboxSessionRegistry, mcpRegistry }
|
|
530
|
+
= createRemoteExecutionServices({
|
|
531
|
+
agentManager, workingDirectory, hookDispatcher, configManager, runtimeBus: options.runtimeBus,
|
|
532
|
+
});
|
|
538
533
|
const tokenAuditor = new ApiTokenAuditor({ managed: false });
|
|
539
534
|
const componentHealthMonitor = new ComponentHealthMonitor();
|
|
540
535
|
const worktreeRegistry = new WorktreeRegistry(workingDirectory);
|
|
@@ -572,7 +567,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
572
567
|
// code-index-services.ts's header doc.
|
|
573
568
|
const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, configManager, memoryEmbeddingRegistry, isReindexPaused: () => pauseController.isPaused('code-index-reindex'), admitExpensiveWork });
|
|
574
569
|
// Store snapshots, the periodic append-only sweep, durable remembered-approval rules + the live credential chain — see durability-services.ts.
|
|
575
|
-
const { storeSnapshotScheduler, appendOnlyRetentionScheduler, userPermissionRuleStore, stopDurabilityHousekeeping } = createDurabilityServices({
|
|
570
|
+
const { storeSnapshotScheduler, appendOnlyRetentionScheduler, userPermissionRuleStore, stopDurabilityHousekeeping, stopConfigWatch } = createDurabilityServices({
|
|
576
571
|
configManager, secretsManager, providerRegistry, memoryDbPath, codeIndexDbPath: codeIndexDbPath(workingDirectory), surface, shellPaths, // + retention-sweep roots & live config watch (mirrors the SDK)
|
|
577
572
|
...(options.currentSessionId ? { currentSessionId: options.currentSessionId } : {}), // exempts the running session from crash-residue reaping
|
|
578
573
|
});
|
|
@@ -618,7 +613,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
618
613
|
const { voiceSetup } = wireVoiceSetup({ configManager, shellPaths, voiceProviders, admitExpensiveWork });
|
|
619
614
|
|
|
620
615
|
// Terminal-shell wrapper over the SDK registerGatewayVerbGroups (gateway-verbs.ts); checkin.*/fleet-needs-input/pairing.* register only when their deps are present. memoryGovernor lights up ops.memory.get; voiceSetup lights up voice.local.status/install.
|
|
621
|
-
attachWsOnlyGatewayVerbHandlers(gatewayMethods, { processRegistry, workspaceCheckpointManager, conversationRewindPort: createSessionConversationRewindPort(), sessionBroker, secretsManager, stepUpService, approvalBroker, requestApproval: (input) => approvalBroker.requestApproval(input), watcherRegistry, userPermissionRuleStore, shellPaths, configManager, runtimeStore: options.runtimeStore, channelDeliveryRouter, providerRegistry, automationManager, sessionLister: sessionBroker, sessionIntake: sessionBroker, workingDirectory, memoryRegistry, pairingTokens, sessionLiveTurnControls, powerManager, memoryGovernor, voiceSetup, relayAvailable: () => configManager.get('relay.enabled') === true, pairingWebOrigin: () => resolvePairingWebOrigin(configManager).origin, ...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }) });
|
|
616
|
+
attachWsOnlyGatewayVerbHandlers(gatewayMethods, { processRegistry, workspaceCheckpointManager, conversationRewindPort: createSessionConversationRewindPort(), sessionBroker, secretsManager, stepUpService, approvalBroker, requestApproval: (input) => approvalBroker.requestApproval(input), watcherRegistry, userPermissionRuleStore, shellPaths, configManager, runtimeStore: options.runtimeStore, channelDeliveryRouter, providerRegistry, automationManager, sessionLister: sessionBroker, sessionIntake: sessionBroker, workingDirectory, memoryRegistry, pairingTokens, sessionLiveTurnControls, powerManager, memoryGovernor, voiceSetup, relayAvailable: () => configManager.get('relay.enabled') === true, pairingWebOrigin: () => resolvePairingWebOrigin(configManager).origin, disposal: disposalScope.registry, ...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }) });
|
|
622
617
|
const integrationHelpers = new IntegrationHelperService({
|
|
623
618
|
surface, // surface-scoped: continuity's recovery-file check must read the SAME paths the app writes with, not the unscoped legacy pair.
|
|
624
619
|
runtimeStore: options.runtimeStore,
|
|
@@ -684,7 +679,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
684
679
|
// bridge (the high-churn 'ops' domain stays out of the wholesale allowlist).
|
|
685
680
|
wireMemoryPressureNotice(options.runtimeBus, notificationDispatcher);
|
|
686
681
|
|
|
687
|
-
|
|
682
|
+
const services: RuntimeServices = {
|
|
688
683
|
workingDirectory,
|
|
689
684
|
homeDirectory,
|
|
690
685
|
surface,
|
|
@@ -762,6 +757,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
762
757
|
toolLLM,
|
|
763
758
|
distributedRuntime,
|
|
764
759
|
daemonHandlers,
|
|
760
|
+
clusterCoordinator,
|
|
761
|
+
clusterGroup,
|
|
762
|
+
startCluster: () => startClusterServices({ clusterGroup, clusterCoordinator }),
|
|
765
763
|
remoteRunnerRegistry,
|
|
766
764
|
remoteSupervisor,
|
|
767
765
|
sessionMemoryStore,
|
|
@@ -796,5 +794,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
796
794
|
workspaceCheckpointManager,
|
|
797
795
|
integrationHelpers,
|
|
798
796
|
rerootStores: createStoreRerooter({ codeIndexStore, projectIndex }),
|
|
797
|
+
dispose: (): void => disposalScope.dispose(),
|
|
799
798
|
};
|
|
799
|
+
registerSurfaceRuntimePollers(disposalScope.registry, services, { stopConfigWatch }); return services;
|
|
800
800
|
}
|
|
@@ -206,9 +206,47 @@ const ENABLEMENT_KEYS_BEHAVIOR_VERIFIED = new Set(
|
|
|
206
206
|
).size;
|
|
207
207
|
|
|
208
208
|
/** Settings with real local behavior verification: the authored baseline plus the persistence-tested feature-knob, device/trigger, and enablement keys. */
|
|
209
|
+
/**
|
|
210
|
+
* Config keys added by the LAN group-key layer: the automatic group-key
|
|
211
|
+
* rotation interval, the dual-generation acceptance window around a rotation,
|
|
212
|
+
* the discovery beacon interval, and the roster gossip interval.
|
|
213
|
+
*
|
|
214
|
+
* THIS CONSTANT IS NOT A DIAL, for the same reason as the two above. Each key
|
|
215
|
+
* is counted because a test that exercises it was actually written:
|
|
216
|
+
* `src/test/verification/cluster-group-settings-persistence.test.ts` runs the
|
|
217
|
+
* same four-part persistence contract — schema default exposure, `set()`
|
|
218
|
+
* through the key's own validator to disk, reload into a fresh ConfigManager
|
|
219
|
+
* with read-back equality, and reset-to-default that survives reload.
|
|
220
|
+
*
|
|
221
|
+
* PER-KEY EVIDENCE. All four have a LIVE consumer in this build, which is more
|
|
222
|
+
* than several of the keys counted above can claim:
|
|
223
|
+
* cluster.keyRotationHours — read by resolveClusterGroupSettings and
|
|
224
|
+
* compared against the current key's age in
|
|
225
|
+
* the group runtime's rotation check.
|
|
226
|
+
* cluster.keyRotationGraceMinutes — sets how long the previous generation
|
|
227
|
+
* stays accepted after a scheduled
|
|
228
|
+
* rotation; drives the keyring's accepted
|
|
229
|
+
* generation set.
|
|
230
|
+
* cluster.beaconSeconds — the discovery beacon timer interval, and
|
|
231
|
+
* the basis of the "recently heard from"
|
|
232
|
+
* window that decides which member mints a
|
|
233
|
+
* rotation.
|
|
234
|
+
* cluster.rosterGossipSeconds — how often the member list is shared.
|
|
235
|
+
*
|
|
236
|
+
* `cluster.enabled` is NOT here: it is a feature enablement key and the ledger
|
|
237
|
+
* counts those in its own set.
|
|
238
|
+
*/
|
|
239
|
+
export const CLUSTER_GROUP_LOCAL_SETTINGS = [
|
|
240
|
+
'cluster.keyRotationHours',
|
|
241
|
+
'cluster.keyRotationGraceMinutes',
|
|
242
|
+
'cluster.beaconSeconds',
|
|
243
|
+
'cluster.rosterGossipSeconds',
|
|
244
|
+
] as const;
|
|
245
|
+
|
|
209
246
|
const SETTINGS_BEHAVIOR_VERIFIED = SETTINGS_BEHAVIOR_BASELINE
|
|
210
247
|
+ FEATURE_KNOB_LOCAL_SETTINGS.length
|
|
211
248
|
+ DEVICE_AND_TRIGGER_LOCAL_SETTINGS.length
|
|
249
|
+
+ CLUSTER_GROUP_LOCAL_SETTINGS.length
|
|
212
250
|
+ ENABLEMENT_KEYS_BEHAVIOR_VERIFIED;
|
|
213
251
|
|
|
214
252
|
const EXTERNAL_SLASH_COMMANDS = new Set([
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '1.
|
|
9
|
+
let _version = '1.24.0';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
// Only trust a version read from OUR OWN package.json. A Bun single-file
|