@pellux/goodvibes-tui 1.17.0 → 1.19.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 +208 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +5025 -1636
- package/package.json +7 -7
- package/src/cli/endpoints.ts +50 -4
- package/src/cli/help.ts +29 -2
- package/src/cli/management-commands.ts +99 -23
- package/src/cli/management.ts +0 -2
- package/src/cli/service-posture.ts +5 -3
- package/src/cli/status.ts +27 -6
- package/src/cli/surface-command.ts +29 -15
- package/src/core/consolidation-receipt.ts +41 -0
- package/src/core/conversation-rendering.ts +14 -5
- package/src/core/memory-diagnostics-gateway.ts +99 -0
- package/src/core/memory-provenance.ts +67 -0
- package/src/core/memory-status.ts +118 -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 +21 -0
- package/src/core/turn-budget-outcome.ts +60 -0
- package/src/core/turn-cancellation.ts +39 -0
- package/src/core/turn-event-wiring.ts +6 -0
- package/src/core/voice-provision-gateway.ts +202 -0
- package/src/core/voice-provision-status.ts +197 -0
- package/src/daemon/cli.ts +127 -60
- package/src/input/command-registry.ts +21 -0
- package/src/input/commands/acp-runtime.ts +72 -0
- package/src/input/commands/devices-runtime.ts +109 -0
- package/src/input/commands/experience-runtime.ts +25 -4
- 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 +15 -1
- package/src/input/commands/operator-rpc.ts +32 -11
- package/src/input/commands/power-runtime.ts +56 -0
- package/src/input/commands/qrcode-runtime.ts +14 -19
- package/src/input/commands/queue-runtime.ts +70 -0
- package/src/input/commands/workstream-attempts.ts +6 -1
- package/src/input/commands/workstream-runtime.ts +44 -25
- package/src/input/commands/worktree-runtime.ts +35 -8
- package/src/input/commands.ts +10 -0
- package/src/input/config-modal-types.ts +11 -0
- package/src/input/config-modal.ts +65 -2
- package/src/input/handler-shortcuts.ts +23 -0
- package/src/input/keybindings.ts +19 -1
- package/src/input/settings-modal-data.ts +31 -37
- package/src/input/settings-modal-types.ts +5 -2
- package/src/input/tts-settings-actions.ts +27 -8
- package/src/input/voice-local-settings.ts +52 -0
- package/src/main.ts +42 -42
- package/src/panels/builtin/operations.ts +54 -1
- package/src/panels/builtin/shared.ts +12 -0
- package/src/panels/builtin-modals.ts +49 -32
- package/src/panels/diff-panel.ts +34 -1
- package/src/panels/fleet-acts.ts +446 -0
- package/src/panels/fleet-gateway.ts +117 -0
- package/src/panels/fleet-observed-render.ts +128 -0
- package/src/panels/fleet-panel-format.ts +120 -4
- package/src/panels/fleet-panel-worktree-detail.ts +14 -0
- package/src/panels/fleet-panel.ts +60 -44
- package/src/panels/fleet-read-model.ts +34 -3
- package/src/panels/fleet-spawn.ts +266 -0
- package/src/panels/fleet-stop.ts +38 -2
- package/src/panels/fleet-tabs.ts +2 -2
- package/src/panels/memory-consolidation-gateway.ts +94 -0
- package/src/panels/modals/devices-modal.ts +161 -0
- package/src/panels/modals/memory-modal.ts +149 -5
- package/src/panels/modals/pairing-modal.ts +198 -68
- package/src/panels/modals/security-modal.ts +6 -0
- package/src/panels/workstream-graph-render.ts +123 -0
- package/src/renderer/composer-fragments.ts +69 -0
- package/src/renderer/settings-modal-helpers.ts +9 -0
- package/src/renderer/settings-modal.ts +4 -2
- package/src/renderer/shell-surface.ts +3 -0
- package/src/renderer/tool-call.ts +12 -6
- package/src/renderer/ui-factory.ts +32 -0
- package/src/runtime/bootstrap-command-context.ts +3 -1
- package/src/runtime/bootstrap-command-parts.ts +3 -1
- package/src/runtime/bootstrap-core.ts +6 -6
- package/src/runtime/bootstrap-shell.ts +7 -0
- package/src/runtime/code-index-services.ts +15 -1
- package/src/runtime/durability-services.ts +36 -1
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/idle-power-services.ts +85 -0
- package/src/runtime/index.ts +2 -0
- package/src/runtime/interaction-seams.ts +109 -0
- package/src/runtime/knowledge-services.ts +94 -0
- package/src/runtime/legacy-daemon-migration.ts +38 -6
- package/src/runtime/legacy-daemon-reconcile.ts +439 -0
- package/src/runtime/memory-governance-services.ts +79 -0
- package/src/runtime/notification-dispatch.ts +28 -0
- package/src/runtime/power-keepawake-remote.ts +75 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +105 -107
- package/src/runtime/ui-services.ts +3 -0
- package/src/runtime/voice-setup-services.ts +104 -0
- package/src/shell/blocking-input.ts +21 -141
- package/src/shell/recovery-input-helpers.ts +53 -166
- package/src/version.ts +11 -2
|
@@ -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
|
@@ -279,8 +279,10 @@ export const loadLastConversation = operations.loadLastConversation;
|
|
|
279
279
|
export const loadRecoveryConversation = operations.loadRecoveryConversation;
|
|
280
280
|
export const writeRecoveryFile = operations.writeRecoveryFile;
|
|
281
281
|
export const deleteRecoveryFile = operations.deleteRecoveryFile;
|
|
282
|
+
export const autoRestoreRecovery = operations.autoRestoreRecovery;
|
|
282
283
|
export const checkRecoveryFile = operations.checkRecoveryFile;
|
|
283
284
|
export const getRecoveryFilePath = operations.getRecoveryFilePath;
|
|
285
|
+
export const getRecoveryDir = operations.getRecoveryDir;
|
|
284
286
|
export const getLastSessionPointerPath = operations.getLastSessionPointerPath;
|
|
285
287
|
export const writeLastSessionPointer = operations.writeLastSessionPointer;
|
|
286
288
|
export const readLastSessionPointer = operations.readLastSessionPointer;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// interaction-seams.ts — the command-context interaction seams and the memory-
|
|
3
|
+
// provenance UI controller, extracted from main.ts (file-size hygiene).
|
|
4
|
+
//
|
|
5
|
+
// The seams expose the in-process orchestrator's per-tool cancel and mid-turn
|
|
6
|
+
// queue edit/delete, the power toggle/status, and the provenance-chip drill-in
|
|
7
|
+
// to the command layer. The memory-provenance controller owns the "used N
|
|
8
|
+
// memories" chip's state (the latest turn's metadata.memory.recordIds and the
|
|
9
|
+
// expand flag), its per-turn capture, and its render.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
13
|
+
import type { PowerManager } from '@pellux/goodvibes-sdk/platform/power';
|
|
14
|
+
import type { Line } from '../types/grid.ts';
|
|
15
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
16
|
+
import { createCancelToolCall, type ToolCancelOrchestrator } from '../core/turn-cancellation.ts';
|
|
17
|
+
import { powerSurfaceFromState } from '../core/power-status.ts';
|
|
18
|
+
import { memoryRecordIdsFromTurn, readMemoryShowProvenance } from '../core/memory-provenance.ts';
|
|
19
|
+
import { UIFactory } from '../renderer/ui-factory.ts';
|
|
20
|
+
|
|
21
|
+
/** The orchestrator surface the interaction seams need. */
|
|
22
|
+
export interface InteractionOrchestrator extends ToolCancelOrchestrator {
|
|
23
|
+
listQueuedMessages(): ReadonlyArray<{ id: string; queuedAt: number; text: string }>;
|
|
24
|
+
editQueuedMessage(id: string, text: string): boolean;
|
|
25
|
+
deleteQueuedMessage(id: string): boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface InteractionSeamDeps {
|
|
29
|
+
readonly orchestrator: InteractionOrchestrator;
|
|
30
|
+
readonly powerManager: PowerManager;
|
|
31
|
+
/** Topology-aware power projection (the chip source): the DAEMON's state in adopted-external mode. Falls back to the local manager when absent. */
|
|
32
|
+
readonly readPowerSurface?: (() => ReturnType<typeof powerSurfaceFromState>) | undefined;
|
|
33
|
+
readonly render: () => void;
|
|
34
|
+
readonly notify: (message: string) => void;
|
|
35
|
+
readonly getActiveToolCallId: () => string | undefined;
|
|
36
|
+
readonly toggleMemoryProvenance: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Wire the per-tool cancel, queue edit/delete, power, and memory-provenance seams onto the command context. */
|
|
40
|
+
export function wireInteractionSeams(cc: CommandContext, deps: InteractionSeamDeps): void {
|
|
41
|
+
cc.cancelToolCall = createCancelToolCall(deps.orchestrator, deps.getActiveToolCallId, () => {
|
|
42
|
+
deps.notify('[Tool] Cancelled the running tool call — the turn continues.');
|
|
43
|
+
deps.render();
|
|
44
|
+
});
|
|
45
|
+
cc.listQueuedMessages = () => deps.orchestrator.listQueuedMessages();
|
|
46
|
+
cc.editQueuedMessage = (id, text) => { const ok = deps.orchestrator.editQueuedMessage(id, text); if (ok) deps.render(); return ok; };
|
|
47
|
+
cc.deleteQueuedMessage = (id) => { const ok = deps.orchestrator.deleteQueuedMessage(id); if (ok) deps.render(); return ok; };
|
|
48
|
+
cc.toggleMemoryProvenance = deps.toggleMemoryProvenance;
|
|
49
|
+
cc.getPowerState = () => (deps.readPowerSurface ? deps.readPowerSurface() : powerSurfaceFromState(deps.powerManager.getState()));
|
|
50
|
+
cc.setKeepAwake = async (enabled) => { const next = powerSurfaceFromState(await deps.powerManager.setKeepAwake(enabled)); deps.render(); return next; };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The memory-provenance chip UI: per-turn capture, drill-in state, and render. */
|
|
54
|
+
export interface MemoryProvenanceUi {
|
|
55
|
+
/** Toggle the drill-in expand state (Alt+M). */
|
|
56
|
+
toggle(): void;
|
|
57
|
+
/** Capture the memory record ids from a completed turn's payload (metadata.memory.recordIds). */
|
|
58
|
+
onTurnCompleted(evt: unknown): void;
|
|
59
|
+
/** The chip lines for the current turn — empty when the setting is off or the turn used no memories. */
|
|
60
|
+
renderChip(width: number, configManager: Pick<ConfigManager, 'get'>): Line[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A resolved provenance record summary (null = the record is no longer available). */
|
|
64
|
+
type ResolvedRecord = { readonly summary: string; readonly cls: string } | null;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create the memory-provenance chip controller (default OFF; reads nothing when
|
|
68
|
+
* off). When the drill-in is expanded it RESOLVES each memory record id to its
|
|
69
|
+
* summary through the memory spine (the same host-or-daemon access the recall
|
|
70
|
+
* commands use, so an adopted daemon resolves over the wire) — per-id and
|
|
71
|
+
* lazily, so one forgotten record never blanks the rest and a collapsed chip
|
|
72
|
+
* costs nothing. Mirrors the webui's provenance detail fetch.
|
|
73
|
+
*/
|
|
74
|
+
export function createMemoryProvenanceUi(deps: {
|
|
75
|
+
readonly render: () => void;
|
|
76
|
+
readonly memorySpine: { get(id: string): Promise<{ summary: string; cls: string } | null> };
|
|
77
|
+
}): MemoryProvenanceUi {
|
|
78
|
+
let latestRecordIds: readonly string[] = [];
|
|
79
|
+
let expanded = false;
|
|
80
|
+
// id -> resolved record (null = not found). Absent = not yet fetched.
|
|
81
|
+
const resolved = new Map<string, ResolvedRecord>();
|
|
82
|
+
const inFlight = new Set<string>();
|
|
83
|
+
|
|
84
|
+
const resolveExpanded = (): void => {
|
|
85
|
+
for (const id of latestRecordIds) {
|
|
86
|
+
if (resolved.has(id) || inFlight.has(id)) continue;
|
|
87
|
+
inFlight.add(id);
|
|
88
|
+
void deps.memorySpine.get(id)
|
|
89
|
+
.then((record) => { resolved.set(id, record ? { summary: record.summary, cls: record.cls } : null); })
|
|
90
|
+
.catch(() => { resolved.set(id, null); })
|
|
91
|
+
.finally(() => { inFlight.delete(id); deps.render(); });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
toggle() {
|
|
97
|
+
expanded = !expanded;
|
|
98
|
+
if (expanded) resolveExpanded();
|
|
99
|
+
deps.render();
|
|
100
|
+
},
|
|
101
|
+
onTurnCompleted(evt) { latestRecordIds = memoryRecordIdsFromTurn(evt); expanded = false; },
|
|
102
|
+
renderChip(width, configManager) {
|
|
103
|
+
if (!readMemoryShowProvenance(configManager) || latestRecordIds.length === 0) return [];
|
|
104
|
+
if (expanded) resolveExpanded(); // lazy: fetch any still-unresolved ids (idempotent)
|
|
105
|
+
const entries = latestRecordIds.map((id) => ({ id, record: resolved.has(id) ? resolved.get(id) : undefined }));
|
|
106
|
+
return UIFactory.createMemoryProvenanceChip(width, latestRecordIds.length, entries, expanded);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// knowledge-services.ts — the knowledge/wiki + home-graph stack (TUI wiring)
|
|
3
|
+
//
|
|
4
|
+
// Constructs the three KnowledgeStores (regular wiki, agent, home-graph), their
|
|
5
|
+
// semantic services, the ingest services, the home-graph service, and the
|
|
6
|
+
// project-planning + work-plan stores. Extracted into its own module rather
|
|
7
|
+
// than built inline in services.ts, which sits at the architecture check's
|
|
8
|
+
// 800-line cap (scripts/check-architecture.ts) — new/large construction blocks
|
|
9
|
+
// get their own module and a single wiring call there (mirrors
|
|
10
|
+
// createWorkstreamServices / createDurabilityServices / createCodeIndexServices).
|
|
11
|
+
//
|
|
12
|
+
// The semantic services + ingest services receive the memory-governor
|
|
13
|
+
// backpressure seams (isBackgroundPaused for the knowledge-self-improvement job
|
|
14
|
+
// + admitExpensiveWork for the critical-tier refusal), mirroring the SDK's own
|
|
15
|
+
// createRuntimeServices. The web-knowledge gap repairer is wired by the caller
|
|
16
|
+
// AFTER webSearchService exists (it is constructed later in services.ts), using
|
|
17
|
+
// the semantic + ingest handles this returns.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
HomeGraphService,
|
|
22
|
+
GOODVIBES_AGENT_KNOWLEDGE_DB_FILE,
|
|
23
|
+
HOME_GRAPH_KNOWLEDGE_EXTENSION,
|
|
24
|
+
KnowledgeService,
|
|
25
|
+
KnowledgeSemanticService,
|
|
26
|
+
KnowledgeStore,
|
|
27
|
+
ProjectPlanningService,
|
|
28
|
+
createProviderBackedKnowledgeSemanticLlm,
|
|
29
|
+
projectPlanningProjectIdFromPath,
|
|
30
|
+
} from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
31
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
32
|
+
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
33
|
+
import type { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
34
|
+
import type { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
|
|
35
|
+
import type { RuntimeEventBus } from '@/runtime/index.ts';
|
|
36
|
+
import { WorkPlanStore } from '../work-plans/work-plan-store.ts';
|
|
37
|
+
|
|
38
|
+
const REGULAR_KNOWLEDGE_DB_FILE = 'knowledge-wiki.sqlite';
|
|
39
|
+
const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
|
|
40
|
+
|
|
41
|
+
export interface KnowledgeServicesDeps {
|
|
42
|
+
readonly configManager: ConfigManager;
|
|
43
|
+
readonly providerRegistry: ProviderRegistry;
|
|
44
|
+
readonly artifactStore: ArtifactStore;
|
|
45
|
+
readonly memoryRegistry: MemoryRegistry;
|
|
46
|
+
readonly runtimeBus: RuntimeEventBus;
|
|
47
|
+
readonly workingDirectory: string;
|
|
48
|
+
readonly homeDirectory: string;
|
|
49
|
+
/** True while the 'knowledge-self-improvement' job is governor-paused. */
|
|
50
|
+
readonly isBackgroundPaused: () => boolean;
|
|
51
|
+
/** Critical-tier admission gate for expensive knowledge work. */
|
|
52
|
+
readonly admitExpensiveWork: (label: string) => { allowed: boolean; reason?: string | undefined };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface KnowledgeServices {
|
|
56
|
+
readonly knowledgeStore: KnowledgeStore;
|
|
57
|
+
readonly agentKnowledgeStore: KnowledgeStore;
|
|
58
|
+
readonly homeGraphKnowledgeStore: KnowledgeStore;
|
|
59
|
+
readonly knowledgeSemanticService: KnowledgeSemanticService;
|
|
60
|
+
readonly homeGraphSemanticService: KnowledgeSemanticService;
|
|
61
|
+
readonly agentKnowledgeSemanticService: KnowledgeSemanticService;
|
|
62
|
+
readonly knowledgeService: KnowledgeService;
|
|
63
|
+
readonly agentKnowledgeService: KnowledgeService;
|
|
64
|
+
readonly homeGraphService: HomeGraphService;
|
|
65
|
+
readonly projectPlanningService: ProjectPlanningService;
|
|
66
|
+
readonly projectPlanningProjectId: string;
|
|
67
|
+
readonly workPlanStore: WorkPlanStore;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Construct the knowledge/wiki + home-graph stack with governor backpressure wired in. */
|
|
71
|
+
export function createKnowledgeServices(deps: KnowledgeServicesDeps): KnowledgeServices {
|
|
72
|
+
const { configManager, providerRegistry, artifactStore, memoryRegistry, runtimeBus, isBackgroundPaused, admitExpensiveWork } = deps;
|
|
73
|
+
const knowledgeStore = new KnowledgeStore({ configManager, dbFileName: REGULAR_KNOWLEDGE_DB_FILE });
|
|
74
|
+
const agentKnowledgeStore = new KnowledgeStore({ configManager, dbFileName: GOODVIBES_AGENT_KNOWLEDGE_DB_FILE });
|
|
75
|
+
const homeGraphKnowledgeStore = new KnowledgeStore({ configManager, dbFileName: HOME_GRAPH_KNOWLEDGE_DB_FILE });
|
|
76
|
+
const knowledgeSemanticLlm = createProviderBackedKnowledgeSemanticLlm(providerRegistry, { timeoutMs: 20_000, maxConcurrent: 1 });
|
|
77
|
+
const knowledgeSemanticService = new KnowledgeSemanticService(knowledgeStore, { llm: knowledgeSemanticLlm, maxLlmSourcesPerReindex: 3, isBackgroundPaused, admitExpensiveWork });
|
|
78
|
+
const homeGraphSemanticService = new KnowledgeSemanticService(homeGraphKnowledgeStore, { llm: knowledgeSemanticLlm, maxLlmSourcesPerReindex: 3, objectProfiles: HOME_GRAPH_KNOWLEDGE_EXTENSION.objectProfiles, isBackgroundPaused, admitExpensiveWork });
|
|
79
|
+
const agentKnowledgeSemanticService = new KnowledgeSemanticService(agentKnowledgeStore, { llm: knowledgeSemanticLlm, maxLlmSourcesPerReindex: 3, isBackgroundPaused, admitExpensiveWork });
|
|
80
|
+
const knowledgeService = new KnowledgeService(knowledgeStore, artifactStore, undefined, { memoryRegistry, runtimeBus, semanticService: knowledgeSemanticService, admitExpensiveWork });
|
|
81
|
+
knowledgeService.attachRuntimeBus(runtimeBus);
|
|
82
|
+
const agentKnowledgeService = new KnowledgeService(agentKnowledgeStore, artifactStore, undefined, { memoryRegistry, runtimeBus, semanticService: agentKnowledgeSemanticService, admitExpensiveWork });
|
|
83
|
+
agentKnowledgeService.attachRuntimeBus(runtimeBus);
|
|
84
|
+
const homeGraphService = new HomeGraphService(homeGraphKnowledgeStore, artifactStore, { semanticService: homeGraphSemanticService, admitExpensiveWork });
|
|
85
|
+
const projectPlanningProjectId = projectPlanningProjectIdFromPath(deps.workingDirectory);
|
|
86
|
+
const projectPlanningService = new ProjectPlanningService(knowledgeStore, { defaultProjectId: projectPlanningProjectId });
|
|
87
|
+
const workPlanStore = new WorkPlanStore({ homeDirectory: deps.homeDirectory, projectId: projectPlanningProjectId, projectRoot: deps.workingDirectory });
|
|
88
|
+
return {
|
|
89
|
+
knowledgeStore, agentKnowledgeStore, homeGraphKnowledgeStore,
|
|
90
|
+
knowledgeSemanticService, homeGraphSemanticService, agentKnowledgeSemanticService,
|
|
91
|
+
knowledgeService, agentKnowledgeService, homeGraphService,
|
|
92
|
+
projectPlanningService, projectPlanningProjectId, workPlanStore,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* exercised deterministically via fakes and never touches a real running
|
|
42
42
|
* service in tests.
|
|
43
43
|
*/
|
|
44
|
-
import { existsSync, rmSync } from 'node:fs';
|
|
44
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
45
45
|
import { spawnSync } from 'node:child_process';
|
|
46
46
|
import net from 'node:net';
|
|
47
47
|
import { basename, dirname, join } from 'node:path';
|
|
@@ -118,12 +118,22 @@ export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServi
|
|
|
118
118
|
homeDir: params.homeDir,
|
|
119
119
|
surfaceRoot: 'tui',
|
|
120
120
|
});
|
|
121
|
+
// The unit's ExecStart deliberately carries NO endpoint flags
|
|
122
|
+
// (--hostname/--port): the daemon resolves controlPlane.hostMode/host/port
|
|
123
|
+
// from the user's settings at boot, so a later config change (or a host
|
|
124
|
+
// already configured for hostMode=network / a non-default port) keeps its
|
|
125
|
+
// endpoint without a unit rewrite. Baking endpoint values here is what
|
|
126
|
+
// silently re-pinned custom-configured hosts back to the values current at
|
|
127
|
+
// install time. scripts/install.sh writes the same shape — the two paths
|
|
128
|
+
// must produce the identical running daemon (see the installer parity test).
|
|
129
|
+
// `params.host`/`params.port` remain inputs because the migration engine
|
|
130
|
+
// still needs them for its read-only port-liveness probe.
|
|
121
131
|
const definition: ManagedServiceDefinition = {
|
|
122
132
|
name: MANAGED_SERVICE_NAME,
|
|
123
133
|
description: MANAGED_SERVICE_DESCRIPTION,
|
|
124
134
|
workingDirectory,
|
|
125
135
|
command: params.binaryPath,
|
|
126
|
-
args: ['--daemon-home', params.homeDir
|
|
136
|
+
args: ['--daemon-home', params.homeDir],
|
|
127
137
|
env: {},
|
|
128
138
|
restartOnFailure: true,
|
|
129
139
|
};
|
|
@@ -217,8 +227,7 @@ export function detectLegacyUnit(input: DetectLegacyUnitInput): LegacyUnitInfo {
|
|
|
217
227
|
const path = legacyUnitPath(input.homeDir);
|
|
218
228
|
const fileExists = input.legacyUnitFileExists ?? existsSync;
|
|
219
229
|
if (!fileExists(path)) return { present: false, active: false, path };
|
|
220
|
-
const run: ManagedServiceActionRunner = input.actionRunner
|
|
221
|
-
?? ((command, args) => spawnSync(command, args, { stdio: 'pipe', encoding: 'utf-8' }) as ManagedServiceActionResult);
|
|
230
|
+
const run: ManagedServiceActionRunner = input.actionRunner ?? defaultActionRunner(SYSTEMCTL_TIMEOUT_MS);
|
|
222
231
|
const result = run('systemctl', ['--user', 'is-active', `${LEGACY_SERVICE_UNIT_NAME}.service`]);
|
|
223
232
|
const state = (result.stdout ?? '').trim();
|
|
224
233
|
const active = (result.status ?? 1) === 0 && state === 'active';
|
|
@@ -270,6 +279,30 @@ export function legacyUnitNote(legacy: LegacyUnitInfo, trackedServiceName: strin
|
|
|
270
279
|
);
|
|
271
280
|
}
|
|
272
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Hard ceiling on every systemctl invocation made through a DEFAULT action
|
|
284
|
+
* runner in this module. The reconcile below runs on the daemon's own startup
|
|
285
|
+
* path, and `spawnSync` without a timeout blocks the single JS event loop for
|
|
286
|
+
* as long as the child runs — a wedged user D-Bus (a real incident class on
|
|
287
|
+
* this host) would freeze an already-listening daemon indefinitely. A timed-out
|
|
288
|
+
* call reports `status: null`, which every status check in this module treats
|
|
289
|
+
* as failure, so a wedge degrades to an honest refusal instead of a hang.
|
|
290
|
+
*/
|
|
291
|
+
export const SYSTEMCTL_TIMEOUT_MS = 5_000;
|
|
292
|
+
|
|
293
|
+
export function defaultActionRunner(timeoutMs: number): ManagedServiceActionRunner {
|
|
294
|
+
return (command, args) =>
|
|
295
|
+
spawnSync(command, args, { stdio: 'pipe', encoding: 'utf-8', timeout: timeoutMs }) as ManagedServiceActionResult;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Parse a `systemctl show -p MainPID --value` reply: a positive integer pid, or undefined when absent/unparseable/0. */
|
|
299
|
+
export function parseMainPid(result: { status?: number | null; stdout?: string | null | undefined }): number | undefined {
|
|
300
|
+
if ((result.status ?? 1) !== 0) return undefined;
|
|
301
|
+
const parsed = Number.parseInt((result.stdout ?? '').trim(), 10);
|
|
302
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
273
306
|
/**
|
|
274
307
|
* Read-only, best-effort TCP connect probe used ONLY by the legacy-absent
|
|
275
308
|
* branch to tell "nothing is listening on this port" apart from "an
|
|
@@ -490,8 +523,7 @@ export async function runLegacyDaemonMigration(
|
|
|
490
523
|
}
|
|
491
524
|
|
|
492
525
|
// New unit verified healthy — now, and only now, retire the legacy unit.
|
|
493
|
-
const run: ManagedServiceActionRunner = params.actionRunner
|
|
494
|
-
?? ((command, args) => spawnSync(command, args, { stdio: 'pipe', encoding: 'utf-8' }) as ManagedServiceActionResult);
|
|
526
|
+
const run: ManagedServiceActionRunner = params.actionRunner ?? defaultActionRunner(SYSTEMCTL_TIMEOUT_MS);
|
|
495
527
|
const stopResult = run('systemctl', ['--user', 'stop', `${LEGACY_SERVICE_UNIT_NAME}.service`]);
|
|
496
528
|
const disableResult = run('systemctl', ['--user', 'disable', `${LEGACY_SERVICE_UNIT_NAME}.service`]);
|
|
497
529
|
const removeFile = params.legacyUnitFileRemove ?? ((path: string) => rmSync(path, { force: true }));
|