@pellux/goodvibes-daemon 1.28.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 +383 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/bin/goodvibes-daemon +100 -0
- package/bin/launcher-support.js +226 -0
- package/package.json +96 -0
- package/scripts/check-bun.sh +20 -0
- package/scripts/postinstall.js +244 -0
- package/src/cli/command-catalog.ts +828 -0
- package/src/cli/completion.ts +299 -0
- package/src/cli/help.ts +167 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/parser.ts +55 -0
- package/src/cli/surface-catalog.ts +26 -0
- package/src/cli/types.ts +63 -0
- package/src/cluster/daemon-ws-call.ts +235 -0
- package/src/cluster/raw-reply-route.ts +111 -0
- package/src/config/checkpoint-settings.ts +113 -0
- package/src/config/run-daemon-config-migration.ts +47 -0
- package/src/config/secret-config.ts +175 -0
- package/src/config/secrets.ts +71 -0
- package/src/config/surface.ts +24 -0
- package/src/core/pairing-banner.ts +82 -0
- package/src/daemon/cli.ts +878 -0
- package/src/daemon/config-command.ts +281 -0
- package/src/daemon/handlers/context.ts +29 -0
- package/src/daemon/handlers/contracts.ts +43 -0
- package/src/daemon/handlers/credentials.ts +139 -0
- package/src/daemon/handlers/drafts/draft-store.ts +427 -0
- package/src/daemon/handlers/drafts/index.ts +17 -0
- package/src/daemon/handlers/drafts/register.ts +331 -0
- package/src/daemon/handlers/errors.ts +18 -0
- package/src/daemon/handlers/inbox/aggregator.ts +375 -0
- package/src/daemon/handlers/inbox/cursor-store.ts +512 -0
- package/src/daemon/handlers/inbox/index.ts +221 -0
- package/src/daemon/handlers/inbox/mapping.ts +192 -0
- package/src/daemon/handlers/inbox/poller.ts +239 -0
- package/src/daemon/handlers/inbox/provider-adapter.ts +171 -0
- package/src/daemon/handlers/inbox/providers/discord.ts +276 -0
- package/src/daemon/handlers/inbox/providers/email.ts +176 -0
- package/src/daemon/handlers/inbox/providers/imap-client.ts +300 -0
- package/src/daemon/handlers/inbox/providers/route-util.ts +24 -0
- package/src/daemon/handlers/inbox/providers/slack.ts +287 -0
- package/src/daemon/handlers/index.ts +117 -0
- package/src/daemon/handlers/register.ts +180 -0
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +143 -0
- package/src/daemon/handlers/remote/backends/docker.ts +79 -0
- package/src/daemon/handlers/remote/backends/index.ts +40 -0
- package/src/daemon/handlers/remote/backends/local-process.ts +113 -0
- package/src/daemon/handlers/remote/backends/process-runner.ts +127 -0
- package/src/daemon/handlers/remote/backends/ssh.ts +126 -0
- package/src/daemon/handlers/remote/backends/types.ts +97 -0
- package/src/daemon/handlers/remote/dispatcher.ts +181 -0
- package/src/daemon/handlers/remote/index.ts +120 -0
- package/src/daemon/handlers/remote/peer-registry.ts +357 -0
- package/src/daemon/handlers/remote/service.ts +191 -0
- package/src/daemon/handlers/routing/inbox-bridge.ts +71 -0
- package/src/daemon/handlers/routing/index.ts +261 -0
- package/src/daemon/handlers/routing/route-store.ts +319 -0
- package/src/daemon/handlers/routing/routing-resolver.ts +75 -0
- package/src/daemon/handlers/sqlite-store.ts +303 -0
- package/src/daemon/handlers/triage/index.ts +57 -0
- package/src/daemon/handlers/triage/integration.ts +213 -0
- package/src/daemon/handlers/triage/pipeline.ts +274 -0
- package/src/daemon/handlers/triage/scorer.ts +287 -0
- package/src/daemon/handlers/triage/tagger/discord.ts +187 -0
- package/src/daemon/handlers/triage/tagger/imap.ts +384 -0
- package/src/daemon/handlers/triage/tagger/index.ts +184 -0
- package/src/daemon/handlers/triage/tagger/shared.ts +70 -0
- package/src/daemon/handlers/triage/tagger/slack.ts +69 -0
- package/src/daemon/handlers/triage/types.ts +50 -0
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/local-daemon-state.ts +233 -0
- package/src/daemon/pair-command.ts +301 -0
- package/src/daemon/provision-wake-model.ts +81 -0
- package/src/daemon/send/channels.ts +200 -0
- package/src/daemon/send/command.ts +333 -0
- package/src/daemon/send/composition.ts +100 -0
- package/src/daemon/send/failure-text.ts +93 -0
- package/src/daemon/send/inert-text.ts +225 -0
- package/src/daemon/send/stdin.ts +24 -0
- package/src/daemon/service-commands.ts +530 -0
- package/src/daemon/sessions-command.ts +209 -0
- package/src/daemon/status-command.ts +481 -0
- package/src/daemon/webui-command.ts +339 -0
- package/src/runtime/boot-tasks.ts +110 -0
- package/src/runtime/cluster-composition.ts +124 -0
- package/src/runtime/cluster-group-composition.ts +284 -0
- package/src/runtime/conversation-rewind-port.ts +171 -0
- package/src/runtime/credential-composition.ts +54 -0
- package/src/runtime/daemon-handler-composition.ts +76 -0
- package/src/runtime/device-posture-composition.ts +115 -0
- package/src/runtime/disposal-wiring.ts +101 -0
- package/src/runtime/fleet-needs-input-push.ts +61 -0
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/hosted-session-composition.ts +128 -0
- package/src/runtime/index.ts +100 -0
- package/src/runtime/knowledge-services.ts +101 -0
- package/src/runtime/legacy-daemon-migration.ts +605 -0
- package/src/runtime/legacy-daemon-reconcile.ts +448 -0
- package/src/runtime/mail-composition.ts +65 -0
- package/src/runtime/notification-dispatch.ts +86 -0
- package/src/runtime/plugin-composition.ts +111 -0
- package/src/runtime/runtime-services-types.ts +268 -0
- package/src/runtime/services.ts +756 -0
- package/src/runtime/trigger-services.ts +62 -0
- package/src/runtime/trust/checkpoint-eligibility.ts +138 -0
- package/src/runtime/trust/trust-gated-approvals.ts +169 -0
- package/src/runtime/update-check.ts +61 -0
- package/src/runtime/workspace-checkpointing.ts +116 -0
- package/src/testing/daemon-fixture.ts +276 -0
- package/src/testing/hosted-session-failures.ts +92 -0
- package/src/version.ts +26 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* device-posture-composition.ts — the paired-phone feature inside THIS daemon.
|
|
3
|
+
*
|
|
4
|
+
* A phone pairs with whichever daemon the person runs. Until this module
|
|
5
|
+
* existed this daemon had no device posture at all: `device.nodes.maxPaired`
|
|
6
|
+
* was enforced at the pairing path (SDK-side) and `device.capabilities.mode`
|
|
7
|
+
* was an onboarding toggle, while the other eleven `device.*` keys were
|
|
8
|
+
* recorded, read back, and governed nothing — the capability service they
|
|
9
|
+
* describe was never built here, so there was nothing for them to govern.
|
|
10
|
+
*
|
|
11
|
+
* The feature itself is platform-owned (`platform/devices`): the settings→policy
|
|
12
|
+
* mapping, the grants ledger, the capture store, the housekeeping sweeps, the
|
|
13
|
+
* confirmation flow, and the `phone` tool. This module supplies the three seams
|
|
14
|
+
* that are actually ours and nothing else:
|
|
15
|
+
*
|
|
16
|
+
* - the peer transport — the same DistributedRuntimeManager the remote surface
|
|
17
|
+
* pairs devices onto, so a phone paired here is a node here,
|
|
18
|
+
* - the approval path — the shared approval broker, so the confirmation
|
|
19
|
+
* appears wherever the person is looking (terminal, web app, companion),
|
|
20
|
+
* - the storage root and the live config manager.
|
|
21
|
+
*
|
|
22
|
+
* Constructing this starts nothing. `startHousekeeping()` is called from the
|
|
23
|
+
* bootstrap tail: grants and captures both outlive a restart, so a grant whose
|
|
24
|
+
* phone is gone, or a capture torn by a crash, is reaped BEFORE the first
|
|
25
|
+
* request of this run is served — and the periodic sweep after it is what keeps
|
|
26
|
+
* a long-running daemon from going days without one. A failed sweep is logged
|
|
27
|
+
* and the app still runs: housekeeping failing is a reason to say so, not a
|
|
28
|
+
* reason to refuse to start.
|
|
29
|
+
*/
|
|
30
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
31
|
+
import { createDevicePostureRuntime, registerDevicePhoneTool } from '@pellux/goodvibes-sdk/platform/devices';
|
|
32
|
+
import type {
|
|
33
|
+
DeviceApprovalBridge,
|
|
34
|
+
DevicePeerTransport,
|
|
35
|
+
DevicePhoneToolRegistry,
|
|
36
|
+
DevicePostureRuntime,
|
|
37
|
+
} from '@pellux/goodvibes-sdk/platform/devices';
|
|
38
|
+
import { registerDevicesGatewayMethods } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
39
|
+
import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
40
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
41
|
+
|
|
42
|
+
/** Who this surface records in the device audit trail. */
|
|
43
|
+
export const DAEMON_DEVICE_ACTOR = 'daemon:phone-tool';
|
|
44
|
+
|
|
45
|
+
export interface DevicePostureCompositionOptions {
|
|
46
|
+
readonly configManager: ConfigManager;
|
|
47
|
+
/** The runtime devices pair onto; its listPeers/invokePeer are the transport. */
|
|
48
|
+
readonly distributedRuntime: DevicePeerTransport;
|
|
49
|
+
readonly approvals: DeviceApprovalBridge;
|
|
50
|
+
/** Surface-scoped directory the grants ledger, captures and disclosure live in. */
|
|
51
|
+
readonly stateDirectory: string;
|
|
52
|
+
/**
|
|
53
|
+
* Binding the catalog turns the whole devices.* family from
|
|
54
|
+
* cataloged-but-unhandled into real handlers: nodes.list, grants.*,
|
|
55
|
+
* housekeeping.run, and — since a surface with no device runtime of its own
|
|
56
|
+
* could read the grants and never open a camera — capability.request and
|
|
57
|
+
* artifacts.list/read. That is what makes the paired phone usable from the web
|
|
58
|
+
* app and the companion rather than only through this process's own tool.
|
|
59
|
+
*
|
|
60
|
+
* The runtime is handed over whole because the verbs and the tool must reach
|
|
61
|
+
* the SAME service: a second path to a phone would be a second place the
|
|
62
|
+
* confirmation prompt and the durable grants could be decided differently.
|
|
63
|
+
*/
|
|
64
|
+
readonly gatewayMethods?: GatewayMethodCatalog | undefined;
|
|
65
|
+
readonly getSessionId?: (() => string | undefined) | undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface DevicePostureServices {
|
|
69
|
+
readonly devicePosture: DevicePostureRuntime;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Build the device posture runtime and bind the device verbs to it. */
|
|
73
|
+
export function createDevicePostureServices(options: DevicePostureCompositionOptions): DevicePostureServices {
|
|
74
|
+
const devicePosture = createDevicePostureRuntime({
|
|
75
|
+
transport: options.distributedRuntime,
|
|
76
|
+
approvals: options.approvals,
|
|
77
|
+
config: options.configManager,
|
|
78
|
+
stateDirectory: options.stateDirectory,
|
|
79
|
+
actor: DAEMON_DEVICE_ACTOR,
|
|
80
|
+
...(options.getSessionId ? { getSessionId: options.getSessionId } : {}),
|
|
81
|
+
});
|
|
82
|
+
if (options.gatewayMethods) registerDevicesGatewayMethods(options.gatewayMethods, devicePosture);
|
|
83
|
+
return { devicePosture };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Everything a host with a tool registry has to do once it is up: register the
|
|
88
|
+
* `phone` tool — the only path that reaches the capability service, so without it
|
|
89
|
+
* the posture keys govern nothing a session can observe — and start housekeeping.
|
|
90
|
+
*
|
|
91
|
+
* One call, because these two belong to the same feature and a host that did the
|
|
92
|
+
* first and forgot the second would serve requests while never reaping a grant
|
|
93
|
+
* whose phone is gone.
|
|
94
|
+
*/
|
|
95
|
+
export function installDevicePosture(
|
|
96
|
+
toolRegistry: DevicePhoneToolRegistry,
|
|
97
|
+
devicePosture: DevicePostureRuntime,
|
|
98
|
+
): void {
|
|
99
|
+
registerDevicePhoneTool(toolRegistry, devicePosture);
|
|
100
|
+
startDeviceHousekeeping(devicePosture);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The recovery sweep plus the periodic timer. Separate from construction so
|
|
105
|
+
* composing a runtime in a test starts no timer and touches no disk, and
|
|
106
|
+
* separate from the call above so this daemon — which registers no tools —
|
|
107
|
+
* still sweeps.
|
|
108
|
+
*/
|
|
109
|
+
export function startDeviceHousekeeping(devicePosture: DevicePostureRuntime): void {
|
|
110
|
+
void devicePosture.startHousekeeping().catch((error: unknown) => {
|
|
111
|
+
logger.warn('Device housekeeping sweep failed at startup', {
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* disposal-wiring.ts
|
|
3
|
+
*
|
|
4
|
+
* Teardown for every poller the daemon's runtime graph starts.
|
|
5
|
+
*
|
|
6
|
+
* The mechanics — the ordered, best-effort, idempotent scope and the
|
|
7
|
+
* all-required owner list — live in the SDK (`platform/runtime/disposal`), so
|
|
8
|
+
* the daemon and the SDK's own composition root cannot drift into two different
|
|
9
|
+
* ideas of what "stop the graph" means. What lives here is only the mapping from
|
|
10
|
+
* the daemon's assembled graph onto that list, plus the four pollers the daemon
|
|
11
|
+
* owns that the SDK's composition does not have at all: the crash-residue sweep,
|
|
12
|
+
* the device housekeeping sweep, the wake-model recovery sweep, and the product
|
|
13
|
+
* handler surfaces with their inbox timers. Calling the SDK's list alone would
|
|
14
|
+
* silently leave every one of them running.
|
|
15
|
+
*
|
|
16
|
+
* It is a separate module because `runtime/services.ts` sits against the repo's
|
|
17
|
+
* 800-line source cap (check-architecture.ts) with no room for the wiring.
|
|
18
|
+
*
|
|
19
|
+
* Ownership note, and the reason any of this exists: this product builds the
|
|
20
|
+
* graph and hands the SAME object to `DaemonServer`. The SDK facade disposes
|
|
21
|
+
* only a graph it constructed itself, so it deliberately leaves this one alone
|
|
22
|
+
* — nothing upstream will ever stop these pollers. The shutdown paths in
|
|
23
|
+
* daemon/cli.ts and the one-shot CLI commands are the only things that can.
|
|
24
|
+
*
|
|
25
|
+
* The owner type below is declared structurally rather than imported from
|
|
26
|
+
* services.ts: that module imports this one, and a type-only edge is still a
|
|
27
|
+
* cycle to the architecture check.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
createDisposalScope,
|
|
32
|
+
registerRuntimePollers,
|
|
33
|
+
type DisposalRegistry,
|
|
34
|
+
type RuntimePollerOwners,
|
|
35
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/disposal';
|
|
36
|
+
|
|
37
|
+
/** Re-exported so the composition root reaches the whole seam through one import. */
|
|
38
|
+
export { createDisposalScope };
|
|
39
|
+
|
|
40
|
+
/** The assembled graph, narrowed to the poller owners it exposes as fields. */
|
|
41
|
+
export interface DaemonRuntimePollerOwners extends Omit<RuntimePollerOwners, 'stopConfigWatch'> {
|
|
42
|
+
/** Daemon-only: the repeating crash-residue sweep. */
|
|
43
|
+
readonly stopDurabilityHousekeeping: () => void;
|
|
44
|
+
/**
|
|
45
|
+
* Daemon-only: the wake-word recovery sweep and a pending boot provision
|
|
46
|
+
* Started only when an entrypoint opted into boot
|
|
47
|
+
* provisioning, and a no-op otherwise — but it is on this list unconditionally,
|
|
48
|
+
* because "the graph did not start it this time" is not a reason for the
|
|
49
|
+
* teardown path to have no way to stop it.
|
|
50
|
+
*/
|
|
51
|
+
readonly stopWakeHousekeeping: () => void;
|
|
52
|
+
/**
|
|
53
|
+
* Daemon-only: the product handler surfaces (daemon-handler-composition.ts).
|
|
54
|
+
*
|
|
55
|
+
* `unregister()` detaches the gateway handlers AND stops the two pollers this
|
|
56
|
+
* product's inbox surface owns — the retention sweep inside `InboxCursorStore`
|
|
57
|
+
* and the per-provider `InboundPoller` intervals. The SDK does not know either
|
|
58
|
+
* exists, so if this surface does not stop them nothing does.
|
|
59
|
+
*/
|
|
60
|
+
readonly daemonHandlers: { readonly unregister: () => void };
|
|
61
|
+
/**
|
|
62
|
+
* This daemon only: the paired-phone feature's housekeeping timer
|
|
63
|
+
* (device-posture-composition.ts). Started by whichever entry point boots the
|
|
64
|
+
* host, so the stop belongs on this list rather than in one shutdown path.
|
|
65
|
+
*/
|
|
66
|
+
readonly devicePosture: { readonly stopHousekeeping: () => void };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The poller owners this daemon holds that are NOT reachable from the
|
|
71
|
+
* assembled graph — handles the factory keeps as locals.
|
|
72
|
+
*/
|
|
73
|
+
export interface RuntimeDisposalExtras {
|
|
74
|
+
/** Handle returned by `ConfigManager.watchConfigFiles()`. */
|
|
75
|
+
readonly stopConfigWatch: () => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Register the stop call for every poller the graph started.
|
|
80
|
+
*
|
|
81
|
+
* `services` is the fully-assembled graph, which already exposes each poller
|
|
82
|
+
* owner as a field — so a poller whose owner reaches the public surface is
|
|
83
|
+
* wired by name rather than by threading another local out of the factory.
|
|
84
|
+
*/
|
|
85
|
+
export function registerDaemonRuntimePollers(
|
|
86
|
+
registry: DisposalRegistry,
|
|
87
|
+
services: DaemonRuntimePollerOwners,
|
|
88
|
+
extras: RuntimeDisposalExtras,
|
|
89
|
+
): void {
|
|
90
|
+
registerRuntimePollers(registry, { ...services, stopConfigWatch: extras.stopConfigWatch });
|
|
91
|
+
registry.add('durability housekeeping', services.stopDurabilityHousekeeping);
|
|
92
|
+
registry.add('device housekeeping', () => services.devicePosture.stopHousekeeping());
|
|
93
|
+
registry.add('wake-word housekeeping', services.stopWakeHousekeeping);
|
|
94
|
+
// Registered LAST so it tears down FIRST (the scope unwinds in reverse), which
|
|
95
|
+
// is the order daemon/cli.ts already used by hand: release the handler surfaces
|
|
96
|
+
// — closing the inbox store and stopping its poll timers — before the pollers
|
|
97
|
+
// the rest of the graph owns. Being on this list is what makes a plain
|
|
98
|
+
// `dispose()` total: every shutdown path stops these, not just the one that
|
|
99
|
+
// remembered to call `unregister()` itself.
|
|
100
|
+
registry.add('daemon handler surfaces', () => services.daemonHandlers.unregister());
|
|
101
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fleet-needs-input-push.ts — wires the fleet "needs-input" browser-push fan-out
|
|
3
|
+
* that the SDK's registerGatewayVerbGroups (reached through the terminal-shell's
|
|
4
|
+
* attachWsOnlyGatewayVerbHandlers wrapper) gates on two deps: `runtimeBus` and
|
|
5
|
+
* `sessionPresence` (see push/service.ts's attachFleetNeedsInputSource). When a
|
|
6
|
+
* fleet node blocks on the operator, it pushes an "Input needed" notification
|
|
7
|
+
* carrying the session/node deep link to every registered device — suppressed
|
|
8
|
+
* while an operator surface is already attached to that node's session.
|
|
9
|
+
*
|
|
10
|
+
* TWO seams have to be live for a real push, not just descriptor presence:
|
|
11
|
+
* 1. Fleet lifecycle deltas (FLEET_NODE_BLOCKED_ON_USER etc.) have to reach the
|
|
12
|
+
* runtime bus's 'fleet' domain in the first place. That is
|
|
13
|
+
* attachFleetEmitBridge, diffing the process registry's own coalesced
|
|
14
|
+
* snapshot tick into per-node lifecycle events. The terminal-shell package's
|
|
15
|
+
* createArchivableFleetRegistry only builds the registry — it does not
|
|
16
|
+
* attach the bridge — so the composition root that owns both the registry
|
|
17
|
+
* and the bus (this file) is the one place responsible for it. The SDK's
|
|
18
|
+
* own daemon composition (platform/runtime/services.ts) attaches this same
|
|
19
|
+
* bridge right after building its process registry.
|
|
20
|
+
* 2. registerGatewayVerbGroups has to be handed `runtimeBus` (to subscribe onto
|
|
21
|
+
* that domain) and `sessionPresence` (to suppress a push when an operator is
|
|
22
|
+
* already looking at the session).
|
|
23
|
+
*
|
|
24
|
+
* sessionPresence is the SDK's own hasFreshSurfaceParticipant: a session counts
|
|
25
|
+
* as attended when any participant heartbeated within the SDK's freshness
|
|
26
|
+
* window. This is the one implementation, imported, so every surface's
|
|
27
|
+
* push/suppress behavior agrees.
|
|
28
|
+
*/
|
|
29
|
+
import { attachFleetEmitBridge } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
30
|
+
import type { ArchivableProcessRegistry } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
31
|
+
import { hasFreshSurfaceParticipant, SURFACE_ROUTE_FRESHNESS_MS, type SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
32
|
+
import type { RuntimeEventBus } from '@/runtime/index.ts';
|
|
33
|
+
|
|
34
|
+
export interface FleetNeedsInputPushDeps {
|
|
35
|
+
readonly runtimeBus: RuntimeEventBus;
|
|
36
|
+
readonly sessionPresence: { isAttached(sessionId: string): boolean };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Attach the fleet emit-bridge (registry snapshot deltas -> runtime bus `fleet`
|
|
41
|
+
* domain) and build the presence lookup, returning both as the deps
|
|
42
|
+
* `attachWsOnlyGatewayVerbHandlers` spreads into its call so the needs-input
|
|
43
|
+
* push source registers for real instead of gracefully degrading to absent.
|
|
44
|
+
*/
|
|
45
|
+
export function wireFleetNeedsInputPush(input: {
|
|
46
|
+
readonly registry: ArchivableProcessRegistry;
|
|
47
|
+
readonly runtimeBus: RuntimeEventBus;
|
|
48
|
+
readonly sessionBroker: Pick<SharedSessionBroker, 'getSession'>;
|
|
49
|
+
}): FleetNeedsInputPushDeps {
|
|
50
|
+
attachFleetEmitBridge({ registry: input.registry, bus: input.runtimeBus });
|
|
51
|
+
const sessionBroker = input.sessionBroker;
|
|
52
|
+
return {
|
|
53
|
+
runtimeBus: input.runtimeBus,
|
|
54
|
+
sessionPresence: {
|
|
55
|
+
isAttached: (sessionId: string) => {
|
|
56
|
+
const session = sessionBroker.getSession(sessionId);
|
|
57
|
+
return session !== null && hasFreshSurfaceParticipant(session, Date.now(), SURFACE_ROUTE_FRESHNESS_MS);
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -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,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hosted-session-composition.ts — what this daemon states so it may host
|
|
3
|
+
* conversation loops.
|
|
4
|
+
*
|
|
5
|
+
* The engine is the SDK's (`@pellux/goodvibes-sdk/platform/hosted-sessions`):
|
|
6
|
+
* lifecycle, the detach policy, the bounded disk state, the verbs, the
|
|
7
|
+
* lifecycle channel. It is off until a product says how a workspace FLOOR is
|
|
8
|
+
* built, and that is deliberate — the floor's `requestApproval` seam is where a
|
|
9
|
+
* product's trust posture lives, and no default can stand in for a decision
|
|
10
|
+
* about who may write files and run commands in a directory a client named over
|
|
11
|
+
* the wire.
|
|
12
|
+
*
|
|
13
|
+
* This daemon's answer is the gate it already had: the workspace trust
|
|
14
|
+
* question is put in front of every ask a hosted run makes (trust/trust-gated-
|
|
15
|
+
* approvals.ts): an undecided workspace raises the question as an ordinary
|
|
16
|
+
* approval record for whichever surface is attached to answer, a trusted one is
|
|
17
|
+
* exactly as permissive as before, and a restricted one refuses non-read
|
|
18
|
+
* categories without asking. What this file adds is PER-WORKSPACE scope: the
|
|
19
|
+
* daemon's own gate reads `<its cwd>/.goodvibes/<surface>/trust.json`, and a
|
|
20
|
+
* session hosted in another directory has to be gated by that directory's
|
|
21
|
+
* decision, not by the daemon's.
|
|
22
|
+
*
|
|
23
|
+
* Everything else in a floor comes from `createClientRuntimeServices` — the
|
|
24
|
+
* same composition a terminal runs — so a hosted turn's tools, hooks, plugins
|
|
25
|
+
* and model stack are the ones the platform already has, not a second set.
|
|
26
|
+
*/
|
|
27
|
+
import { createShellPathService } from '@/runtime/index.ts';
|
|
28
|
+
import { createClientRuntimeServices } from '@pellux/goodvibes-sdk/platform/runtime/client-services';
|
|
29
|
+
import { createRuntimeStore } from '@pellux/goodvibes-sdk/platform/runtime/store';
|
|
30
|
+
import { createLaunchTolerantProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
31
|
+
import type { DaemonHostedSessionsOptions } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
32
|
+
import type { HostedWorkspaceFloor } from '@pellux/goodvibes-sdk/platform/hosted-sessions';
|
|
33
|
+
import { operations } from '@pellux/goodvibes-sdk/platform/runtime';
|
|
34
|
+
const { WorkspaceTrustManager } = operations;
|
|
35
|
+
import { createWorkspaceTrustDecisionAsk, trustGatedApprovalRaiser, type ApprovalRaise } from './trust/trust-gated-approvals.ts';
|
|
36
|
+
import { GOODVIBES_DAEMON_SURFACE_ROOT } from '../config/surface.ts';
|
|
37
|
+
import type { RuntimeServices } from './runtime-services-types.ts';
|
|
38
|
+
|
|
39
|
+
/** The operator policy a session hosted by THIS daemon runs under. */
|
|
40
|
+
function hostedSystemPrompt(input: { readonly workspaceRoot: string }): string {
|
|
41
|
+
return [
|
|
42
|
+
'You are a GoodVibes session hosted by the daemon rather than by a terminal.',
|
|
43
|
+
`Your working directory is ${input.workspaceRoot}.`,
|
|
44
|
+
'Someone may be attached and watching this turn, or may have detached and read it later —',
|
|
45
|
+
'write for both. Say what you did and why; never report work you did not do.',
|
|
46
|
+
'Tool permissions are decided by whoever is attached: an ask you raise may take a while to be',
|
|
47
|
+
'answered, and an unanswered one is a refusal, not a reason to find another way round.',
|
|
48
|
+
].join(' ');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build the hosted-session options this daemon passes to `DaemonServer`.
|
|
53
|
+
*
|
|
54
|
+
* `services` is the daemon's own graph: its config manager, event bus, secrets
|
|
55
|
+
* home and approval broker are shared with every floor, so a hosted session
|
|
56
|
+
* reads the same settings, publishes on the same bus and raises asks into the
|
|
57
|
+
* same broker every other surface is watching.
|
|
58
|
+
*/
|
|
59
|
+
export function createHostedSessionOptions(services: RuntimeServices): DaemonHostedSessionsOptions {
|
|
60
|
+
/**
|
|
61
|
+
* One trust manager per workspace, cached: `load()` is idempotent but a
|
|
62
|
+
* manager per floor keeps the decision file and the in-memory answer for a
|
|
63
|
+
* workspace in one place, so two sessions in one directory cannot end up
|
|
64
|
+
* having been asked the trust question twice.
|
|
65
|
+
*/
|
|
66
|
+
const trustByWorkspace = new Map<string, operations.WorkspaceTrustManager>();
|
|
67
|
+
|
|
68
|
+
const gateFor = (workspaceRoot: string): ApprovalRaise => {
|
|
69
|
+
const shellPaths = createShellPathService({
|
|
70
|
+
workingDirectory: workspaceRoot,
|
|
71
|
+
homeDirectory: services.homeDirectory,
|
|
72
|
+
});
|
|
73
|
+
let trust = trustByWorkspace.get(workspaceRoot);
|
|
74
|
+
if (!trust) {
|
|
75
|
+
trust = new WorkspaceTrustManager({ shellPaths, surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT });
|
|
76
|
+
trustByWorkspace.set(workspaceRoot, trust);
|
|
77
|
+
}
|
|
78
|
+
const raise: ApprovalRaise = (input) => services.approvalBroker.requestApproval(input);
|
|
79
|
+
return trustGatedApprovalRaiser(
|
|
80
|
+
trust,
|
|
81
|
+
raise,
|
|
82
|
+
// The question names the workspace it is about, which is the whole point
|
|
83
|
+
// of scoping it here: a daemon hosting three sessions in three
|
|
84
|
+
// directories asks three separate questions, each answerable on its own.
|
|
85
|
+
createWorkspaceTrustDecisionAsk({ requestApproval: raise, workingDirectory: workspaceRoot }),
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
floorFactory: ({ workspaceRoot }): HostedWorkspaceFloor => {
|
|
91
|
+
const floor = createClientRuntimeServices({
|
|
92
|
+
configManager: services.configManager,
|
|
93
|
+
// The daemon's own bus: a hosted turn's stream events reach the
|
|
94
|
+
// control-plane SSE subscribers through the same path every other
|
|
95
|
+
// runtime event takes, stamped with the hosted session's id.
|
|
96
|
+
runtimeBus: services.runtimeBus,
|
|
97
|
+
// A store of its own. The daemon's runtime store describes the daemon's
|
|
98
|
+
// single local runtime, and a hosted session is not that runtime.
|
|
99
|
+
runtimeStore: createRuntimeStore(),
|
|
100
|
+
featureFlags: services.featureFlags,
|
|
101
|
+
surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT,
|
|
102
|
+
workingDir: workspaceRoot,
|
|
103
|
+
homeDirectory: services.homeDirectory,
|
|
104
|
+
requestApproval: gateFor(workspaceRoot),
|
|
105
|
+
// A workspace with broken or absent provider credentials must degrade,
|
|
106
|
+
// not take the daemon down on a create call.
|
|
107
|
+
providerRegistryFactory: createLaunchTolerantProviderRegistry,
|
|
108
|
+
});
|
|
109
|
+
// The machine's own local models. The daemon's registry learned them at
|
|
110
|
+
// boot (the persisted discovery cache) and from the LAN scan; a floor
|
|
111
|
+
// builds its own registry and would otherwise be the only place on this
|
|
112
|
+
// box where they are not routable. Servers found by a scan that finishes
|
|
113
|
+
// AFTER a floor is built reach the next floor, not this one — stated
|
|
114
|
+
// rather than hidden, because a wrong claim here would look like a model
|
|
115
|
+
// that exists everywhere except in hosted sessions.
|
|
116
|
+
const discovered = services.providerRegistry.listDiscoveredServers();
|
|
117
|
+
if (discovered.length > 0) floor.providerRegistry.registerDiscoveredProviders([...discovered]);
|
|
118
|
+
return {
|
|
119
|
+
services: floor,
|
|
120
|
+
// The daemon has a real review-chain controller; a hosted session's
|
|
121
|
+
// orchestrator lists through it rather than reporting none.
|
|
122
|
+
wrfcController: services.wrfcController,
|
|
123
|
+
dispose: (): void => floor.dispose(),
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
systemPrompt: hostedSystemPrompt,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime barrel for the daemon product.
|
|
3
|
+
*
|
|
4
|
+
* The SDK groups its runtime seams into namespace exports (`bootstrap`,
|
|
5
|
+
* `observability`, `operations`, `security`, `shell`, `state`, `transport`,
|
|
6
|
+
* `ui`). This file names the ones the daemon composition actually uses so the
|
|
7
|
+
* composition modules import them as plain symbols, and so there is exactly one
|
|
8
|
+
* place to look when the SDK moves one.
|
|
9
|
+
*
|
|
10
|
+
* It re-exports and nothing else: no local runtime entry points, no wrappers, no
|
|
11
|
+
* behavior. Anything that needs behavior belongs in its own module or in the
|
|
12
|
+
* SDK.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
bootstrap,
|
|
17
|
+
observability,
|
|
18
|
+
operations,
|
|
19
|
+
security,
|
|
20
|
+
shell,
|
|
21
|
+
transport,
|
|
22
|
+
ui,
|
|
23
|
+
} from '@pellux/goodvibes-sdk/platform/runtime';
|
|
24
|
+
|
|
25
|
+
// State, the event bus and the read-model store.
|
|
26
|
+
export * from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
27
|
+
export * from '@pellux/goodvibes-sdk/platform/runtime/store';
|
|
28
|
+
export * from '@pellux/goodvibes-sdk/platform/runtime/feature-flags';
|
|
29
|
+
export * from '@pellux/goodvibes-sdk/platform/runtime/settings';
|
|
30
|
+
export * from '@pellux/goodvibes-sdk/platform/runtime/sandbox';
|
|
31
|
+
|
|
32
|
+
// Shell paths + the surface-scoped storage handle.
|
|
33
|
+
export const createShellPathService = shell.createShellPathService;
|
|
34
|
+
export type ShellPathService = shell.ShellPathService;
|
|
35
|
+
export const createSessionSurface = operations.createSessionSurface;
|
|
36
|
+
export type SessionSurface = operations.SessionSurface;
|
|
37
|
+
export const WorktreeRegistry = shell.WorktreeRegistry;
|
|
38
|
+
export type WorktreeRegistry = shell.WorktreeRegistry;
|
|
39
|
+
|
|
40
|
+
// Remote execution + the distributed runtime.
|
|
41
|
+
export const DistributedRuntimeManager = operations.DistributedRuntimeManager;
|
|
42
|
+
export type DistributedRuntimeManager = operations.DistributedRuntimeManager;
|
|
43
|
+
export const RemoteRunnerRegistry = operations.RemoteRunnerRegistry;
|
|
44
|
+
export type RemoteRunnerRegistry = operations.RemoteRunnerRegistry;
|
|
45
|
+
export const RemoteSupervisor = operations.RemoteSupervisor;
|
|
46
|
+
export type RemoteSupervisor = operations.RemoteSupervisor;
|
|
47
|
+
|
|
48
|
+
// Boot helpers the daemon runs itself (the facade does not know about them).
|
|
49
|
+
export const syncConfiguredServices = bootstrap.synchronizeConfiguredServices;
|
|
50
|
+
|
|
51
|
+
// Observability.
|
|
52
|
+
export const TelemetryApiService = observability.TelemetryApiService;
|
|
53
|
+
export type TelemetryApiService = observability.TelemetryApiService;
|
|
54
|
+
export const ComponentHealthMonitor = observability.ComponentHealthMonitor;
|
|
55
|
+
export type ComponentHealthMonitor = observability.ComponentHealthMonitor;
|
|
56
|
+
export const IdempotencyStore = observability.IdempotencyStore;
|
|
57
|
+
export type IdempotencyStore = observability.IdempotencyStore;
|
|
58
|
+
|
|
59
|
+
// Security.
|
|
60
|
+
export const PolicyRuntimeState = security.PolicyRuntimeState;
|
|
61
|
+
export type PolicyRuntimeState = security.PolicyRuntimeState;
|
|
62
|
+
|
|
63
|
+
// Integration helpers (surface-scoped continuity reads) and the no-op screen
|
|
64
|
+
// stand-ins. The daemon facade's service-graph contract names a panel manager
|
|
65
|
+
// and a keybindings manager because a surface that has a screen supplies real
|
|
66
|
+
// ones; the daemon has no screen, and the SDK ships the honest no-ops for
|
|
67
|
+
// exactly this case rather than leaving a hole a fake would fill.
|
|
68
|
+
export const IntegrationHelperService = ui.IntegrationHelperService;
|
|
69
|
+
export type IntegrationHelperService = ui.IntegrationHelperService;
|
|
70
|
+
export const createNoopPanelManager = ui.createNoopPanelManager;
|
|
71
|
+
export const createNoopKeybindingsManager = ui.createNoopKeybindingsManager;
|
|
72
|
+
export type PanelManagerLike = ui.PanelManagerLike;
|
|
73
|
+
export type KeybindingsManagerLike = ui.KeybindingsManagerLike;
|
|
74
|
+
|
|
75
|
+
// Notification routing types (the router itself is imported from its own subpath).
|
|
76
|
+
export type Notification = ui.Notification;
|
|
77
|
+
export type RoutingDecision = ui.RoutingDecision;
|
|
78
|
+
|
|
79
|
+
// Outbound network transport installation (proxy/TLS policy from settings).
|
|
80
|
+
export const GlobalNetworkTransportInstaller = transport.GlobalNetworkTransportInstaller;
|
|
81
|
+
export type GlobalNetworkTransportInstaller = transport.GlobalNetworkTransportInstaller;
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
// Runtime event payload unions, re-exported so a consumer names one import path
|
|
85
|
+
// for the bus and the shapes that travel on it.
|
|
86
|
+
export type {
|
|
87
|
+
AgentEvent,
|
|
88
|
+
CommunicationEvent,
|
|
89
|
+
OpsEvent,
|
|
90
|
+
OrchestrationEvent,
|
|
91
|
+
PermissionEvent,
|
|
92
|
+
ProviderEvent,
|
|
93
|
+
RouteEvent,
|
|
94
|
+
SessionEvent,
|
|
95
|
+
TaskEvent,
|
|
96
|
+
ToolEvent,
|
|
97
|
+
TransportEvent,
|
|
98
|
+
TurnEvent,
|
|
99
|
+
WorkflowEvent,
|
|
100
|
+
} from '@pellux/goodvibes-sdk/events';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// knowledge-services.ts — the knowledge/wiki + home-graph stack
|
|
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 '@pellux/goodvibes-sdk/platform/workflow';
|
|
37
|
+
import { GOODVIBES_DAEMON_SURFACE_ROOT } from '../config/surface.ts';
|
|
38
|
+
|
|
39
|
+
const REGULAR_KNOWLEDGE_DB_FILE = 'knowledge-wiki.sqlite';
|
|
40
|
+
const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
|
|
41
|
+
|
|
42
|
+
export interface KnowledgeServicesDeps {
|
|
43
|
+
readonly configManager: ConfigManager;
|
|
44
|
+
readonly providerRegistry: ProviderRegistry;
|
|
45
|
+
readonly artifactStore: ArtifactStore;
|
|
46
|
+
readonly memoryRegistry: MemoryRegistry;
|
|
47
|
+
readonly runtimeBus: RuntimeEventBus;
|
|
48
|
+
readonly workingDirectory: string;
|
|
49
|
+
readonly homeDirectory: string;
|
|
50
|
+
/** True while the 'knowledge-self-improvement' job is governor-paused. */
|
|
51
|
+
readonly isBackgroundPaused: () => boolean;
|
|
52
|
+
/** Critical-tier admission gate for expensive knowledge work. */
|
|
53
|
+
readonly admitExpensiveWork: (label: string) => { allowed: boolean; reason?: string | undefined };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface KnowledgeServices {
|
|
57
|
+
readonly knowledgeStore: KnowledgeStore;
|
|
58
|
+
readonly agentKnowledgeStore: KnowledgeStore;
|
|
59
|
+
readonly homeGraphKnowledgeStore: KnowledgeStore;
|
|
60
|
+
readonly knowledgeSemanticService: KnowledgeSemanticService;
|
|
61
|
+
readonly homeGraphSemanticService: KnowledgeSemanticService;
|
|
62
|
+
readonly agentKnowledgeSemanticService: KnowledgeSemanticService;
|
|
63
|
+
readonly knowledgeService: KnowledgeService;
|
|
64
|
+
readonly agentKnowledgeService: KnowledgeService;
|
|
65
|
+
readonly homeGraphService: HomeGraphService;
|
|
66
|
+
readonly projectPlanningService: ProjectPlanningService;
|
|
67
|
+
readonly projectPlanningProjectId: string;
|
|
68
|
+
readonly workPlanStore: WorkPlanStore;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Construct the knowledge/wiki + home-graph stack with governor backpressure wired in. */
|
|
72
|
+
export function createKnowledgeServices(deps: KnowledgeServicesDeps): KnowledgeServices {
|
|
73
|
+
const { configManager, providerRegistry, artifactStore, memoryRegistry, runtimeBus, isBackgroundPaused, admitExpensiveWork } = deps;
|
|
74
|
+
const knowledgeStore = new KnowledgeStore({ configManager, dbFileName: REGULAR_KNOWLEDGE_DB_FILE });
|
|
75
|
+
const agentKnowledgeStore = new KnowledgeStore({ configManager, dbFileName: GOODVIBES_AGENT_KNOWLEDGE_DB_FILE });
|
|
76
|
+
const homeGraphKnowledgeStore = new KnowledgeStore({ configManager, dbFileName: HOME_GRAPH_KNOWLEDGE_DB_FILE });
|
|
77
|
+
const knowledgeSemanticLlm = createProviderBackedKnowledgeSemanticLlm(providerRegistry, { timeoutMs: 20_000, maxConcurrent: 1 });
|
|
78
|
+
const knowledgeSemanticService = new KnowledgeSemanticService(knowledgeStore, { llm: knowledgeSemanticLlm, maxLlmSourcesPerReindex: 3, isBackgroundPaused, admitExpensiveWork });
|
|
79
|
+
const homeGraphSemanticService = new KnowledgeSemanticService(homeGraphKnowledgeStore, { llm: knowledgeSemanticLlm, maxLlmSourcesPerReindex: 3, objectProfiles: HOME_GRAPH_KNOWLEDGE_EXTENSION.objectProfiles, isBackgroundPaused, admitExpensiveWork });
|
|
80
|
+
const agentKnowledgeSemanticService = new KnowledgeSemanticService(agentKnowledgeStore, { llm: knowledgeSemanticLlm, maxLlmSourcesPerReindex: 3, isBackgroundPaused, admitExpensiveWork });
|
|
81
|
+
const knowledgeService = new KnowledgeService(knowledgeStore, artifactStore, undefined, { memoryRegistry, runtimeBus, semanticService: knowledgeSemanticService, admitExpensiveWork });
|
|
82
|
+
knowledgeService.attachRuntimeBus(runtimeBus);
|
|
83
|
+
const agentKnowledgeService = new KnowledgeService(agentKnowledgeStore, artifactStore, undefined, { memoryRegistry, runtimeBus, semanticService: agentKnowledgeSemanticService, admitExpensiveWork });
|
|
84
|
+
agentKnowledgeService.attachRuntimeBus(runtimeBus);
|
|
85
|
+
const homeGraphService = new HomeGraphService(homeGraphKnowledgeStore, artifactStore, { semanticService: homeGraphSemanticService, admitExpensiveWork });
|
|
86
|
+
const projectPlanningProjectId = projectPlanningProjectIdFromPath(deps.workingDirectory);
|
|
87
|
+
const projectPlanningService = new ProjectPlanningService(knowledgeStore, { defaultProjectId: projectPlanningProjectId });
|
|
88
|
+
const workPlanStore = new WorkPlanStore({
|
|
89
|
+
homeDirectory: deps.homeDirectory,
|
|
90
|
+
surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT,
|
|
91
|
+
projectId: projectPlanningProjectId,
|
|
92
|
+
projectRoot: deps.workingDirectory,
|
|
93
|
+
source: GOODVIBES_DAEMON_SURFACE_ROOT,
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
knowledgeStore, agentKnowledgeStore, homeGraphKnowledgeStore,
|
|
97
|
+
knowledgeSemanticService, homeGraphSemanticService, agentKnowledgeSemanticService,
|
|
98
|
+
knowledgeService, agentKnowledgeService, homeGraphService,
|
|
99
|
+
projectPlanningService, projectPlanningProjectId, workPlanStore,
|
|
100
|
+
};
|
|
101
|
+
}
|