@pellux/goodvibes-tui 1.16.1 → 1.18.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 +138 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +5490 -1806
- 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/management-commands.ts +82 -15
- package/src/cli/management.ts +0 -2
- 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/consolidation-receipt.ts +41 -0
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +18 -9
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/memory-provenance.ts +67 -0
- package/src/core/pairing-devices.ts +49 -0
- package/src/core/pairing-handoff.ts +101 -0
- package/src/core/pairing-offers.ts +83 -0
- package/src/core/pairing-origin.ts +78 -0
- package/src/core/pairing-tailscale-gateway.ts +56 -0
- package/src/core/power-chip-source.ts +91 -0
- package/src/core/power-status.ts +95 -0
- package/src/core/stable-host.ts +171 -0
- package/src/core/stream-event-wiring.ts +29 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-budget-outcome.ts +60 -0
- package/src/core/turn-cancellation.ts +40 -1
- package/src/core/turn-event-wiring.ts +14 -8
- package/src/daemon/cli.ts +64 -58
- 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 +55 -9
- package/src/input/commands/acp-runtime.ts +72 -0
- 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/devices-runtime.ts +109 -0
- package/src/input/commands/git-runtime.ts +28 -4
- package/src/input/commands/graph-runtime.ts +45 -0
- 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/operator-rpc.ts +32 -11
- 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/power-runtime.ts +56 -0
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/qrcode-runtime.ts +14 -19
- package/src/input/commands/queue-runtime.ts +70 -0
- 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-attempts.ts +6 -1
- package/src/input/commands/workstream-runtime.ts +46 -27
- package/src/input/commands/worktree-runtime.ts +35 -8
- package/src/input/commands.ts +10 -2
- package/src/input/config-modal-types.ts +14 -3
- package/src/input/config-modal.ts +75 -13
- 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 +29 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +21 -3
- 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 +52 -57
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +27 -17
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +90 -90
- 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 +83 -11
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +16 -4
- package/src/panels/builtin-modals.ts +59 -42
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +39 -6
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-acts.ts +446 -0
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-gateway.ts +117 -0
- package/src/panels/fleet-observed-render.ts +128 -0
- package/src/panels/fleet-panel-format.ts +163 -11
- package/src/panels/fleet-panel-worktree-detail.ts +14 -0
- package/src/panels/fleet-panel.ts +73 -57
- package/src/panels/fleet-read-model.ts +84 -14
- package/src/panels/fleet-spawn.ts +266 -0
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +41 -5
- package/src/panels/fleet-tabs.ts +15 -16
- 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/memory-consolidation-gateway.ts +94 -0
- package/src/panels/modals/devices-modal.ts +161 -0
- 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 +153 -9
- 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 +198 -68
- 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 +7 -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/panels/workstream-graph-render.ts +123 -0
- 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/composer-fragments.ts +69 -0
- 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 +23 -5
- package/src/renderer/settings-modal.ts +129 -39
- package/src/renderer/shell-surface.ts +6 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +13 -7
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +38 -1
- package/src/runtime/bootstrap-command-context.ts +7 -2
- package/src/runtime/bootstrap-command-parts.ts +9 -5
- package/src/runtime/bootstrap-core.ts +41 -41
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +13 -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 +90 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/idle-power-services.ts +85 -0
- package/src/runtime/index.ts +6 -21
- package/src/runtime/interaction-seams.ts +109 -0
- 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/power-keepawake-remote.ts +75 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- 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 +113 -113
- 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 +6 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +108 -132
- package/src/shell/recovery-input-helpers.ts +53 -166
- 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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* durability-services.ts — the data-safety and remembered-decision stores the
|
|
3
|
+
* daemon composition wires (mirrors the SDK's own createRuntimeServices):
|
|
4
|
+
*
|
|
5
|
+
* - StoreSnapshotScheduler: a daily snapshot of every SQLite store this
|
|
6
|
+
* runtime writes (memory store, memory vector index, code index store),
|
|
7
|
+
* bounded by the retention engine. Timers are unref'd so an undisposed
|
|
8
|
+
* scheduler cannot pin the event loop; hosts that tear down a runtime
|
|
9
|
+
* stop() it themselves.
|
|
10
|
+
* - UserPermissionRuleStore: durable user-origin permission rules
|
|
11
|
+
* (remembered approvals) — one store per project, shared by every
|
|
12
|
+
* PermissionManager built on this runtime; permissions.rules.*
|
|
13
|
+
* lists/deletes. Background init is fail-safe (asks just prompt).
|
|
14
|
+
* - One credential chain (env -> secrets -> subscription): boot applies
|
|
15
|
+
* secrets-backed keys; every secrets write/delete re-registers builtins
|
|
16
|
+
* LIVE (no restart) — badges/picker/chat read the same instances.
|
|
17
|
+
* - Start-time retention janitor: one best-effort append-only sweep over
|
|
18
|
+
* EVERY root the composition knows (working dir, surface root, home,
|
|
19
|
+
* logDir, telemetryDir) — omitting logDir/telemetryDir/home would silently
|
|
20
|
+
* skip the activity-log, telemetry-ledger, and recovery-snapshot stores.
|
|
21
|
+
* - Live config-file watch: external edits to the settings file apply through
|
|
22
|
+
* the same subscribe() pipeline an in-process set() uses — no restart. The
|
|
23
|
+
* underlying watchers are unref'd, so this never pins the event loop.
|
|
24
|
+
*/
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { operations } from '@pellux/goodvibes-sdk/platform/runtime';
|
|
27
|
+
import { resolveMemoryVectorDbPath } from '@pellux/goodvibes-sdk/platform/state';
|
|
28
|
+
import { StoreSnapshotScheduler } from '@pellux/goodvibes-sdk/platform/state/store-snapshots';
|
|
29
|
+
import { UserPermissionRuleStore } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
30
|
+
import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
31
|
+
|
|
32
|
+
export interface DurabilityServicesInput {
|
|
33
|
+
readonly configManager: {
|
|
34
|
+
getControlPlaneConfigDir(): string;
|
|
35
|
+
get(key: never): unknown;
|
|
36
|
+
watchConfigFiles(options?: { intervalMs?: number }): () => void;
|
|
37
|
+
};
|
|
38
|
+
readonly secretsManager: { onDidChange(listener: (key: string) => void): () => void };
|
|
39
|
+
readonly providerRegistry: { refreshProviderCredentials(): Promise<void> };
|
|
40
|
+
readonly memoryDbPath: string;
|
|
41
|
+
readonly codeIndexDbPath: string;
|
|
42
|
+
/** Retention-sweep roots (mirrors the SDK's own createRuntimeServices roots). */
|
|
43
|
+
readonly workingDirectory: string;
|
|
44
|
+
readonly surfaceRoot: string;
|
|
45
|
+
readonly homeDirectory: string;
|
|
46
|
+
readonly shellPaths: { resolveUserPath(...segments: string[]): string };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface DurabilityServices {
|
|
50
|
+
readonly storeSnapshotScheduler: StoreSnapshotScheduler;
|
|
51
|
+
readonly userPermissionRuleStore: UserPermissionRuleStore;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createDurabilityServices(input: DurabilityServicesInput): DurabilityServices {
|
|
55
|
+
const { configManager, secretsManager, providerRegistry } = input;
|
|
56
|
+
secretsManager.onDidChange(() => void providerRegistry.refreshProviderCredentials().catch((error) => logger.warn('live credential refresh failed', { error: summarizeError(error) })));
|
|
57
|
+
void providerRegistry.refreshProviderCredentials().catch((error) => logger.warn('boot credential refresh failed', { error: summarizeError(error) }));
|
|
58
|
+
|
|
59
|
+
const storeSnapshotScheduler = new StoreSnapshotScheduler({
|
|
60
|
+
stores: [
|
|
61
|
+
{ name: 'memory store', dbPath: input.memoryDbPath },
|
|
62
|
+
{ name: 'memory vector index', dbPath: resolveMemoryVectorDbPath(input.memoryDbPath) },
|
|
63
|
+
{ name: 'code index store', dbPath: input.codeIndexDbPath },
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
storeSnapshotScheduler.start();
|
|
67
|
+
|
|
68
|
+
// Start-time retention janitor: pass EVERY root the composition knows so the
|
|
69
|
+
// activity-log, telemetry-ledger, and recovery-snapshot stores are all swept.
|
|
70
|
+
// Best-effort — a retention failure never takes startup down (the sweep
|
|
71
|
+
// swallows its own errors).
|
|
72
|
+
operations.runStartupAppendOnlySweep(
|
|
73
|
+
{
|
|
74
|
+
workingDirectory: input.workingDirectory,
|
|
75
|
+
surfaceRoot: input.surfaceRoot,
|
|
76
|
+
homeDirectory: input.homeDirectory,
|
|
77
|
+
logDir: input.shellPaths.resolveUserPath('logs'),
|
|
78
|
+
telemetryDir: input.shellPaths.resolveUserPath('telemetry'),
|
|
79
|
+
},
|
|
80
|
+
(k: string) => configManager.get(k as never),
|
|
81
|
+
);
|
|
82
|
+
// External config edits apply LIVE through the same subscribe() pipeline an
|
|
83
|
+
// in-process set() uses; the underlying watchers are unref'd.
|
|
84
|
+
configManager.watchConfigFiles();
|
|
85
|
+
|
|
86
|
+
const userPermissionRuleStore = new UserPermissionRuleStore(join(configManager.getControlPlaneConfigDir(), 'permission-rules.json'));
|
|
87
|
+
void userPermissionRuleStore.init().catch((error) => logger.warn('user permission rule store init failed; asks will prompt', { error: summarizeError(error) }));
|
|
88
|
+
|
|
89
|
+
return { storeSnapshotScheduler, userPermissionRuleStore };
|
|
90
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* embedded-service-factories.ts — the factories startExternalServices uses to
|
|
3
|
+
* construct the interactive session's embedded DaemonServer + HttpListener
|
|
4
|
+
* (extracted from bootstrap.ts for file-size hygiene).
|
|
5
|
+
*
|
|
6
|
+
* The embedded DaemonServer suppresses the facade's internal auto-updater:
|
|
7
|
+
* that loop compares the sdk package's version — not this binary's — against
|
|
8
|
+
* this repo's releases, so inside the interactive session it would find an
|
|
9
|
+
* "update" hourly and exit the user's process on "restart". Clients update at
|
|
10
|
+
* launch (cli/launch-auto-update.ts); the embedded daemon runs no update loop
|
|
11
|
+
* at all. See daemon/lifecycle.ts.
|
|
12
|
+
*/
|
|
13
|
+
import { DaemonServer, HttpListener } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
14
|
+
import { createSafeHostServeFactory } from '../daemon/safe-serve.ts';
|
|
15
|
+
import type { startExternalServices } from '@/runtime/index.ts';
|
|
16
|
+
|
|
17
|
+
export type ExternalServiceFactories = NonNullable<Parameters<typeof startExternalServices>[4]>;
|
|
18
|
+
|
|
19
|
+
export function createEmbeddedServiceFactories(sharedDaemonToken: string): ExternalServiceFactories {
|
|
20
|
+
return {
|
|
21
|
+
sharedDaemonToken,
|
|
22
|
+
createDaemonServer: (bus, userAuth, runtimeServices) => {
|
|
23
|
+
// No updateArtifact: the embedded daemon keeps updates host-managed (the
|
|
24
|
+
// TUI client updates itself), so the facade runs no self-update loop.
|
|
25
|
+
const daemonServer = new DaemonServer({
|
|
26
|
+
runtimeBus: bus,
|
|
27
|
+
userAuth,
|
|
28
|
+
runtimeServices,
|
|
29
|
+
serveFactory: createSafeHostServeFactory('Embedded daemon'),
|
|
30
|
+
});
|
|
31
|
+
return daemonServer;
|
|
32
|
+
},
|
|
33
|
+
createHttpListener: (dispatcher, userAuth, configManager) => new HttpListener({
|
|
34
|
+
hookDispatcher: dispatcher,
|
|
35
|
+
userAuth,
|
|
36
|
+
configManager,
|
|
37
|
+
serveFactory: createSafeHostServeFactory('Embedded HTTP listener'),
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feature-settings — TUI-side helpers over the SDK's FEATURE_SETTINGS surface.
|
|
3
|
+
*
|
|
4
|
+
* Every platform capability is configured through a first-class settings key
|
|
5
|
+
* in its natural domain (behavior.compactionStrategy, sandbox.enabled,
|
|
6
|
+
* surfaces.slack.enabled, ...). There is no separate enablement namespace.
|
|
7
|
+
* These helpers answer the recurring TUI questions:
|
|
8
|
+
* - which feature a given id names, and whether a stock config has it on,
|
|
9
|
+
* - whether a raw config value means the feature is currently on,
|
|
10
|
+
* - which single settings write turns a feature on or off.
|
|
11
|
+
*
|
|
12
|
+
* Enablement kinds (from the SDK binding layer):
|
|
13
|
+
* - boolean : the key's boolean value is the enablement.
|
|
14
|
+
* - enum : active while the key's value is in enabledValues (several
|
|
15
|
+
* features can share one key, e.g. behavior.compactionStrategy).
|
|
16
|
+
* - constant: the capability has no separate off switch. When its key is a
|
|
17
|
+
* boolean (the surface enabled keys) that value is the honest
|
|
18
|
+
* user-facing switch; a non-boolean constant key (e.g.
|
|
19
|
+
* fetch.sanitizeMode) has no off position at all.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { CONFIG_SCHEMA, type ConfigKey, type ConfigManager, type ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
23
|
+
import {
|
|
24
|
+
FEATURE_SETTINGS,
|
|
25
|
+
deriveFeatureState,
|
|
26
|
+
type FeatureSetting,
|
|
27
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
28
|
+
|
|
29
|
+
export type { FeatureSetting } from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Lookups
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/** Every capability by id, in SDK declaration order. */
|
|
36
|
+
export const FEATURE_SETTINGS_BY_ID: ReadonlyMap<string, FeatureSetting> = new Map(
|
|
37
|
+
FEATURE_SETTINGS.map((feature) => [feature.id, feature]),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const SCHEMA_BY_KEY: ReadonlyMap<string, ConfigSetting> = new Map(
|
|
41
|
+
CONFIG_SCHEMA.map((setting) => [setting.key, setting]),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export function getFeatureSetting(featureId: string): FeatureSetting | null {
|
|
45
|
+
return FEATURE_SETTINGS_BY_ID.get(featureId) ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The CONFIG_SCHEMA descriptor for a settings key, or null for TUI-local synthetics. */
|
|
49
|
+
export function getConfigSchemaSetting(key: string): ConfigSetting | null {
|
|
50
|
+
return SCHEMA_BY_KEY.get(key) ?? null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whether a stock configuration has the feature active. */
|
|
54
|
+
export function isFeatureDefaultEnabled(featureId: string): boolean {
|
|
55
|
+
return getFeatureSetting(featureId)?.defaultEnabled === true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** All features whose enablement key is `key` (several features can share one). */
|
|
59
|
+
export function featuresForEnablementKey(key: string): readonly FeatureSetting[] {
|
|
60
|
+
return FEATURE_SETTINGS.filter((feature) => feature.enablement.key === key);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Enablement derivation
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether a raw config value means the feature is on, as a settings surface
|
|
69
|
+
* should display it. Boolean/enum kinds derive exactly as the runtime gate
|
|
70
|
+
* does; constant kinds fall back to the boolean key value when there is one
|
|
71
|
+
* (the honest user-facing switch for the surface adapters) and read as always
|
|
72
|
+
* on otherwise.
|
|
73
|
+
*/
|
|
74
|
+
export function isFeatureValueEnabled(feature: FeatureSetting, value: unknown): boolean {
|
|
75
|
+
const { key, kind, enabledValues } = feature.enablement;
|
|
76
|
+
if (kind === 'constant') {
|
|
77
|
+
const schema = getConfigSchemaSetting(key);
|
|
78
|
+
if (schema?.type === 'boolean') return value === true;
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return deriveFeatureState(
|
|
82
|
+
{
|
|
83
|
+
featureId: feature.id,
|
|
84
|
+
key,
|
|
85
|
+
kind,
|
|
86
|
+
...(enabledValues !== undefined ? { enabledValues } : {}),
|
|
87
|
+
},
|
|
88
|
+
value,
|
|
89
|
+
) === 'enabled';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Whether the live config currently has the feature on (see isFeatureValueEnabled). */
|
|
93
|
+
export function isFeatureConfigEnabled(
|
|
94
|
+
configManager: Pick<ConfigManager, 'get'>,
|
|
95
|
+
featureId: string,
|
|
96
|
+
): boolean {
|
|
97
|
+
const feature = getFeatureSetting(featureId);
|
|
98
|
+
if (!feature) return false;
|
|
99
|
+
return isFeatureValueEnabled(feature, configManager.get(feature.enablement.key));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Enablement writes
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
export interface FeatureEnablementWrite {
|
|
107
|
+
readonly key: ConfigKey;
|
|
108
|
+
readonly value: boolean | string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The single settings write that turns a feature on or off, or null when the
|
|
113
|
+
* feature has no off position (a constant-kind capability on a non-boolean
|
|
114
|
+
* key). For enum kinds the schema default is preferred whenever it sits on
|
|
115
|
+
* the requested side, so "enable" lands on the feature's stock mode and
|
|
116
|
+
* "disable" lands on the stock off mode rather than an arbitrary value.
|
|
117
|
+
*/
|
|
118
|
+
export function featureEnablementWrite(
|
|
119
|
+
featureId: string,
|
|
120
|
+
enabled: boolean,
|
|
121
|
+
): FeatureEnablementWrite | null {
|
|
122
|
+
const feature = getFeatureSetting(featureId);
|
|
123
|
+
if (!feature) return null;
|
|
124
|
+
const { key, kind, enabledValues } = feature.enablement;
|
|
125
|
+
|
|
126
|
+
if (kind === 'boolean') return { key, value: enabled };
|
|
127
|
+
|
|
128
|
+
const schema = getConfigSchemaSetting(key);
|
|
129
|
+
if (kind === 'constant') {
|
|
130
|
+
return schema?.type === 'boolean' ? { key, value: enabled } : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// enum
|
|
134
|
+
const active = enabledValues ?? [];
|
|
135
|
+
const allValues = schema?.enumValues ?? [];
|
|
136
|
+
const schemaDefault = typeof schema?.default === 'string' ? schema.default : undefined;
|
|
137
|
+
if (enabled) {
|
|
138
|
+
const target = schemaDefault !== undefined && active.includes(schemaDefault)
|
|
139
|
+
? schemaDefault
|
|
140
|
+
: active[0];
|
|
141
|
+
return target !== undefined ? { key, value: target } : null;
|
|
142
|
+
}
|
|
143
|
+
const offTarget = schemaDefault !== undefined && !active.includes(schemaDefault)
|
|
144
|
+
? schemaDefault
|
|
145
|
+
: allValues.find((value) => !active.includes(value));
|
|
146
|
+
return offTarget !== undefined ? { key, value: offTarget } : null;
|
|
147
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fleet-services.ts — the shared, archive-aware fleet registry construction,
|
|
3
|
+
* lifted out of services.ts so the composition root stays under the file-size
|
|
4
|
+
* cap. Mirrors the sibling create*Services helpers (durability, code-index,
|
|
5
|
+
* workstream): a single dependency-injected call that services.ts makes once.
|
|
6
|
+
*
|
|
7
|
+
* Also owns the daemon-side observed foreign-agent source: when
|
|
8
|
+
* observeExternalAgents is on (the standalone daemon), externally-launched
|
|
9
|
+
* coding-agent sessions observed read-only on the host fold in as
|
|
10
|
+
* 'observed-external' rows (visibility + steer; never counted against
|
|
11
|
+
* fleet.maxSize, never stopped). The interactive process leaves it off and
|
|
12
|
+
* reads the daemon's snapshot, so it never double-scans. Absence ⇒ a quiet
|
|
13
|
+
* empty set. Mirrors the SDK's own createRuntimeServices wiring.
|
|
14
|
+
*/
|
|
15
|
+
import { computeUsageCostUsd, type ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
16
|
+
import { type ArchivableProcessRegistry } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
17
|
+
import { ObservedAgentSource } from '@pellux/goodvibes-sdk/platform/runtime/fleet/observed';
|
|
18
|
+
import { createArchivableFleetRegistry, type ProcessRegistryDeps } from '@pellux/goodvibes-terminal-shell';
|
|
19
|
+
|
|
20
|
+
export interface FleetServicesDeps
|
|
21
|
+
extends Omit<ProcessRegistryDeps, 'observedAgents' | 'priceUsage'> {
|
|
22
|
+
/** Turns on the daemon-side observed foreign-agent source (daemon only). */
|
|
23
|
+
readonly observeExternalAgents?: boolean | undefined;
|
|
24
|
+
/** Backs the honest pricing resolver folded into priceUsage below. */
|
|
25
|
+
readonly providerRegistry: Pick<ProviderRegistry, 'resolveModelPricing'>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createFleetServices(
|
|
29
|
+
deps: FleetServicesDeps,
|
|
30
|
+
): { processRegistry: ArchivableProcessRegistry } {
|
|
31
|
+
const { observeExternalAgents, providerRegistry, ...registryDeps } = deps;
|
|
32
|
+
const observedAgents = observeExternalAgents ? new ObservedAgentSource() : undefined;
|
|
33
|
+
const processRegistry = createArchivableFleetRegistry({
|
|
34
|
+
...registryDeps,
|
|
35
|
+
observedAgents, // Daemon-side observed foreign-agent rows (undefined in the interactive process)
|
|
36
|
+
// Honest-unpriced through the ONE pricing resolver (manual -> registration -> provider-served
|
|
37
|
+
// -> catalog -> unknown); unknown/subscription yields null, never $0. Mirrors the SDK composition.
|
|
38
|
+
priceUsage: (model, usage) => (model ? computeUsageCostUsd(providerRegistry.resolveModelPricing(model), usage) : null),
|
|
39
|
+
});
|
|
40
|
+
return { processRegistry };
|
|
41
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// idle-power-services.ts — the idle-time / sleep-ownership / live-turn slice of
|
|
3
|
+
// the runtime composition, extracted from services.ts (file-size hygiene).
|
|
4
|
+
//
|
|
5
|
+
// Mirrors the SDK's own createRuntimeServices wiring: idle-time memory
|
|
6
|
+
// consolidation (learning.consolidation.*), host sleep ownership (power.*), and
|
|
7
|
+
// the per-session live-turn control holder that backs the
|
|
8
|
+
// sessions.toolCalls.cancel / sessions.queuedMessages.* wire verbs. Consolidation
|
|
9
|
+
// receipts ride the SAME attach-time queue every other receipt uses — a run that
|
|
10
|
+
// changed something records a one-line notice into the file-backed
|
|
11
|
+
// feature-announcement store, drained on the next surface attach; a quiet run
|
|
12
|
+
// records nothing.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
16
|
+
import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
17
|
+
import { MemoryConsolidationScheduler } from '@pellux/goodvibes-sdk/platform/state';
|
|
18
|
+
import { SessionLiveTurnControlsHolder } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
19
|
+
import { PowerManager, wireRuntimePower, createUnavailablePowerSeam } from '@pellux/goodvibes-sdk/platform/power';
|
|
20
|
+
import { FeatureAnnouncementStore, featureAnnouncementsPath } from '@pellux/goodvibes-sdk/platform/runtime/feature-announcements';
|
|
21
|
+
import type { RuntimeEventBus } from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
22
|
+
import { formatConsolidationReceipt } from '../core/consolidation-receipt.ts';
|
|
23
|
+
|
|
24
|
+
export interface IdlePowerServicesDeps {
|
|
25
|
+
readonly configManager: ConfigManager;
|
|
26
|
+
readonly memoryRegistry: MemoryRegistry;
|
|
27
|
+
readonly runtimeBus: RuntimeEventBus;
|
|
28
|
+
/** True when no session is busy — the consolidation scheduler's idle gate. */
|
|
29
|
+
readonly isIdle: () => boolean;
|
|
30
|
+
/** Sleep-edge checkpoint + a wake catch-up tick (store snapshots). */
|
|
31
|
+
readonly snapshotTick: () => void;
|
|
32
|
+
/** Wake catch-up: fire the automation heartbeat. */
|
|
33
|
+
readonly heartbeat: () => Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Host power seam. SDK 1.9.0 makes wireRuntimePower default an absent seam to
|
|
36
|
+
* the REAL host seam (createHostPowerSeam — which spawns systemd-inhibit and a
|
|
37
|
+
* dbus-monitor sleep-edge watcher). That spawn is a host-level side effect, so
|
|
38
|
+
* this fork mirrors the SDK's own createRuntimeServices: when no seam is passed
|
|
39
|
+
* we default to the NON-spawning unavailable seam, keeping test-constructed
|
|
40
|
+
* runtimes deterministic. Only the real long-lived compositions (the standalone
|
|
41
|
+
* daemon and the embedded interactive runtime, which itself IS the daemon) pass
|
|
42
|
+
* createHostPowerSeam() to opt into live OS keep-awake/idle-inhibit.
|
|
43
|
+
*/
|
|
44
|
+
readonly powerSeam?: Parameters<typeof wireRuntimePower>[0]['seam'];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface IdlePowerServices {
|
|
48
|
+
readonly memoryConsolidationScheduler: MemoryConsolidationScheduler;
|
|
49
|
+
readonly powerManager: PowerManager;
|
|
50
|
+
readonly sessionLiveTurnControls: SessionLiveTurnControlsHolder;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Construct (and start) the idle-consolidation scheduler, power manager, and live-turn holder. */
|
|
54
|
+
export function wireIdlePowerAndLiveTurn(deps: IdlePowerServicesDeps): IdlePowerServices {
|
|
55
|
+
const consolidationReceiptStore = new FeatureAnnouncementStore(featureAnnouncementsPath(deps.configManager));
|
|
56
|
+
const memoryConsolidationScheduler = new MemoryConsolidationScheduler({
|
|
57
|
+
memoryRegistry: deps.memoryRegistry,
|
|
58
|
+
configSource: deps.configManager,
|
|
59
|
+
isIdle: deps.isIdle,
|
|
60
|
+
onReceipt: (receipt) => {
|
|
61
|
+
const text = formatConsolidationReceipt(receipt);
|
|
62
|
+
if (text) consolidationReceiptStore.record(receipt.runId, text);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
memoryConsolidationScheduler.start();
|
|
66
|
+
const sessionLiveTurnControls = new SessionLiveTurnControlsHolder();
|
|
67
|
+
const powerManager = wireRuntimePower({
|
|
68
|
+
readConfig: (key) => deps.configManager.get(key as Parameters<typeof deps.configManager.get>[0]),
|
|
69
|
+
writeConfig: (key, value) => deps.configManager.setDynamic(key as Parameters<typeof deps.configManager.setDynamic>[0], value),
|
|
70
|
+
// Live config subscription: an external edit to power.keepAwake (a settings-
|
|
71
|
+
// modal toggle, a hand-edited settings file) applies LIVE to the in-process
|
|
72
|
+
// manager — acquires/releases the inhibitor and lights the chip with no
|
|
73
|
+
// restart. This is the config-apply path for the EMBEDDED topology + the
|
|
74
|
+
// local chip; the external daemon is reached separately over the verb.
|
|
75
|
+
subscribeConfig: (key, cb) => (deps.configManager.subscribe as unknown as (k: string, c: (v: unknown) => void) => () => void)(key, (newValue) => cb(newValue)),
|
|
76
|
+
runtimeBus: deps.runtimeBus,
|
|
77
|
+
sleepCheckpoint: deps.snapshotTick,
|
|
78
|
+
wakeCatchUp: [() => memoryConsolidationScheduler.tick(), deps.snapshotTick, deps.heartbeat],
|
|
79
|
+
// Non-spawning default (see powerSeam doc above): without an explicit opt-in
|
|
80
|
+
// the seam is the honest unavailable one, NOT the spawning host seam that
|
|
81
|
+
// wireRuntimePower would otherwise pick for an undefined seam.
|
|
82
|
+
seam: deps.powerSeam ?? createUnavailablePowerSeam('runtime services constructed without a host power seam'),
|
|
83
|
+
});
|
|
84
|
+
return { memoryConsolidationScheduler, powerManager, sessionLiveTurnControls };
|
|
85
|
+
}
|
package/src/runtime/index.ts
CHANGED
|
@@ -13,10 +13,6 @@ import {
|
|
|
13
13
|
shell,
|
|
14
14
|
transport,
|
|
15
15
|
} from '@pellux/goodvibes-sdk/platform/runtime';
|
|
16
|
-
import {
|
|
17
|
-
createFeatureFlagManager as createSdkFeatureFlagManager,
|
|
18
|
-
FEATURE_FLAG_MAP,
|
|
19
|
-
} from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
20
16
|
import { existsSync, statSync } from 'node:fs';
|
|
21
17
|
import { join, resolve } from 'node:path';
|
|
22
18
|
import type {
|
|
@@ -26,7 +22,6 @@ import type {
|
|
|
26
22
|
shell as Shell,
|
|
27
23
|
transport as Transport,
|
|
28
24
|
} from '@pellux/goodvibes-sdk/platform/runtime';
|
|
29
|
-
import type { FeatureFlagManager, FlagConfig } from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
30
25
|
|
|
31
26
|
// Local runtime entry points.
|
|
32
27
|
export { bootstrapRuntime } from './bootstrap.ts';
|
|
@@ -35,22 +30,10 @@ export type { BootstrapContext } from './bootstrap.ts';
|
|
|
35
30
|
export { createUiRuntimeServices } from './ui-services.ts';
|
|
36
31
|
export type { UiRuntimeServices } from './ui-services.ts';
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
manager.loadFromConfig = (config: FlagConfig): void => {
|
|
43
|
-
originalLoadFromConfig({
|
|
44
|
-
flags: Object.fromEntries(
|
|
45
|
-
Object.entries(config.flags).filter(([id]) => FEATURE_FLAG_MAP.has(id)),
|
|
46
|
-
) as FlagConfig['flags'],
|
|
47
|
-
});
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
return manager;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Public SDK runtime seams.
|
|
33
|
+
// Public SDK runtime seams. createFeatureFlagManager comes straight from the
|
|
34
|
+
// SDK state seam below: gate states derive from domain settings keys
|
|
35
|
+
// (deriveFeatureStates), so the old TUI wrapper that filtered unknown ids out
|
|
36
|
+
// of persisted `featureFlags` overrides has nothing left to filter.
|
|
54
37
|
export * from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
55
38
|
export * from '@pellux/goodvibes-sdk/platform/runtime/store';
|
|
56
39
|
export * from '@pellux/goodvibes-sdk/platform/runtime/ui';
|
|
@@ -296,8 +279,10 @@ export const loadLastConversation = operations.loadLastConversation;
|
|
|
296
279
|
export const loadRecoveryConversation = operations.loadRecoveryConversation;
|
|
297
280
|
export const writeRecoveryFile = operations.writeRecoveryFile;
|
|
298
281
|
export const deleteRecoveryFile = operations.deleteRecoveryFile;
|
|
282
|
+
export const autoRestoreRecovery = operations.autoRestoreRecovery;
|
|
299
283
|
export const checkRecoveryFile = operations.checkRecoveryFile;
|
|
300
284
|
export const getRecoveryFilePath = operations.getRecoveryFilePath;
|
|
285
|
+
export const getRecoveryDir = operations.getRecoveryDir;
|
|
301
286
|
export const getLastSessionPointerPath = operations.getLastSessionPointerPath;
|
|
302
287
|
export const writeLastSessionPointer = operations.writeLastSessionPointer;
|
|
303
288
|
export const readLastSessionPointer = operations.readLastSessionPointer;
|