/** * routes/register-gateway-verb-groups.ts * * One entry point the runtime-services composition root calls to attach every * handler-registered gateway verb group, so services.ts needs a single import * and a single call regardless of how many groups exist. * * It folds in the pre-existing fleet / checkpoints / sessions.search group * (registerFleetCheckpointsSearchGatewayMethods, unchanged) and constructs + wires the browser- * push group: a PushService over the subscription store and VAPID key custody, * its verb handlers, and, the real event source, a subscription to the * approval broker so an approval that needs a decision fans out as a push to the * operator's registered devices. */ import type { GatewayMethodCatalog } from '../method-catalog.js'; import { type FleetCheckpointsSearchGatewayDeps } from './register-fleet-checkpoints-search.js'; import { type PairingGatewayService } from './pairing.js'; import { discoverAcpAgents, type AcpHostService } from '../../acp/host.js'; export { startCiFixSession, startConflictResolutionSession } from './seeded-sessions.js'; import { type TailscaleCommandRunner } from '../../remote-access/tailscale.js'; import type { UserPermissionRuleStore } from '../../permissions/user-rule-store.js'; import { type MemoryProjectionSource } from './memory-projections.js'; import { type CredentialWriteConfig, type CredentialWriteSecrets } from './credentials-write.js'; import { type ApprovalRaiseService } from './approvals-raise.js'; import { type InboundIntakeBroker } from '../../channel-profiles/index.js'; import type { ChannelPolicyManager } from '../../channels/policy-manager.js'; import { type CheckinSessionView } from '../../checkin/index.js'; import type { ProviderRegistry } from '../../providers/registry.js'; import type { AutomationManager } from '../../automation/index.js'; import type { ChannelDeliveryRouter } from '../../channels/delivery-router.js'; import type { CiPollingHost, FixSessionStartOutcome } from '../../ci-watch/index.js'; import type { PermissionPromptDecision, PermissionPromptRequest } from '../../permissions/prompt.js'; import { type StepUpGatewayService } from './stepup.js'; import type { RewindConversationPort } from '../../rewind/index.js'; import { type SessionLiveTurnControlsHolder } from './session-runtime.js'; import { type PowerGatewayService } from './power.js'; import { type DevicesGatewayService } from './devices.js'; import { type MemoryGatewayService } from './memory.js'; import { type VoiceSetupGatewayService } from './voice-setup.js'; import { type BrowserCompositionDeps } from './browser-composition.js'; import { type CalendarGatewayService } from './calendar.js'; import { type EmailGatewayService } from './email.js'; import { type EmailCompositionDeps } from './email-composition.js'; import type { ConfigManager } from '../../config/manager.js'; import type { DisposalRegistry } from '../../runtime/disposal.js'; import type { RuntimeStore } from '../../runtime/store/index.js'; import type { ApprovalSource, NeedsInputPresence, VapidSecretStore } from '../../push/index.js'; import type { RuntimeEventBus } from '../../runtime/events/index.js'; export interface GatewayVerbGroupDeps extends FleetCheckpointsSearchGatewayDeps, BrowserCompositionDeps { /** SecretsManager (get/set), VAPID keypair custody lives here, never in config. */ readonly secretsManager: VapidSecretStore; /** Filled with the owner profile store and occasions service below, which is what lets the `profile` capture tool write. Absent in a host with no agent tools. */ readonly personalCapture?: Pick | undefined; /** * Teardown registry for the pollers this registration starts, today the * push-subscription sweep, which is constructed here and so is otherwise * unreachable from the graph that owns it. Optional: a narrow composition * that never tears down passes nothing and keeps today's behaviour. */ readonly disposal?: DisposalRegistry | undefined; /** The approval broker, the real event source push fans out from. */ readonly approvalBroker: ApprovalSource; /** * Optional: stamp the session an accepted ci fix-this offer spawned onto * its RESOLVED approval record (ApprovalBroker.stampFixSession). Present * when the real broker is wired (the runtime composition root); absent in * narrower compositions, the id then travels only via the channel * notification. */ readonly stampFixSessionOnApproval?: ((offerCallId: string, outcome: FixSessionStartOutcome) => Promise) | undefined; /** * Optional: raise an ask through the shared approval broker. When present, * a watched CI run going red produces a "fix this?" offer whose acceptance * starts the fix-session. Absent → red runs only notify. */ readonly requestApproval?: ((input: { readonly request: PermissionPromptRequest; readonly metadata?: Record | undefined; }) => Promise) | undefined; /** * Optional: the daemon's watcher registry, the recurring-poll host. When * present, registered CI watches are polled on the watchers.ciPollIntervalMs * cadence instead of standing still until a manual ci.watches.run. */ readonly watcherRegistry?: CiPollingHost | undefined; /** * Optional: the durable user-origin permission rule store (remembered * approval decisions). When present, the permissions.rules.* settings verbs * are registered over it; absent, they stay cataloged-but-unhandled. */ readonly userPermissionRuleStore?: Pick | undefined; /** Home-scoped path service; the subscription store file resolves under it. */ readonly shellPaths: { resolveUserPath(...segments: string[]): string; }; /** Surface root every control-plane store here resolves under; required, never defaulted. */ readonly surfaceRoot: string; /** * Optional explicit VAPID JWT `sub` contact, overriding the `push.vapidSubject` * config key. Absent (the normal case) ⇒ the config key is read; empty or * invalid there ⇒ the documented localhost fallback. */ readonly vapidSubject?: string | undefined; /** * Optional: the runtime event bus. When present, a fleet node that becomes * blocked on the operator fans out as a 'needs-input' push (the poll-free * counterpart to the approval source above). Absent → no needs-input pushes * (graceful degrade); every other verb group is unaffected. */ readonly runtimeBus?: Pick | undefined; /** * Optional: operator presence lookup. When present, a needs-input push is * suppressed while an operator surface is actively attached to that node's * session (someone is already looking). Absent → every needs-input block * pushes. */ readonly sessionPresence?: NeedsInputPresence | undefined; /** * Per-pairing token manager. When present, the pairing.tokens.* verbs * (list/create/rename/delete/migrate/revokeShared) are registered over it; * absent → those verbs stay cataloged-but-unhandled (graceful degrade). */ readonly pairingTokens?: PairingGatewayService | undefined; /** Whether the rendezvous relay is available ⇒ the pairing hand-off offers a relay step. */ readonly relayAvailable?: (() => boolean) | undefined; /** The configured web-app origin the pairing QR points at ⇒ hand-off returns a full deep link. */ readonly pairingWebOrigin?: (() => string | undefined) | undefined; /** Injectable tailscale command runner (tests); absent ⇒ the real spawnSync runner. */ readonly tailscaleRunner?: TailscaleCommandRunner | undefined; /** * Optional: the hosted third-party ACP agent service. When present, the * acp.* verbs (discovery + spawn) are registered over it; absent → they stay * cataloged-but-unhandled (graceful degrade for narrower embeds). */ readonly acpHost?: Pick | undefined; /** Injectable discovery seam for the acp verbs (tests); absent ⇒ the real read-only discovery. */ readonly acpDiscover?: (() => ReturnType) | undefined; /** * Config surface backing the session-scoped permission-mode verbs * (sessions.permissionMode.get/set): the daemon's own `permissions.mode` * read/write. A set flows to surfaces as runtime.permissions via the * already-wired mode-change binding. */ readonly configManager: Pick; /** * Runtime store backing sessions.contextUsage.get and the local-session * resolution the session-runtime verbs gate on (getState().session.id). */ readonly runtimeStore: Pick; /** * Optional: the live-turn controls holder an interactive consumer binds its * Orchestrator into. When present, sessions.toolCalls.cancel and the * sessions.queuedMessages.* verbs act on the bound runtime; absent (or * nothing bound) those verbs refuse honestly (LIVE_TURN_CONTROLS_UNAVAILABLE). */ readonly sessionLiveTurnControls?: SessionLiveTurnControlsHolder | undefined; /** Optional: the live PowerManager. When present, power.status.get / power.keepAwake.set serve real state; absent they stay cataloged-but-unhandled. */ readonly powerManager?: PowerGatewayService | undefined; /** Optional: the paired-device capability service. When present, devices.nodes.list / devices.grants.* / devices.housekeeping.run serve real state; absent they stay cataloged-but-unhandled. */ readonly deviceCapabilities?: DevicesGatewayService | undefined; /** Optional: the live MemoryGovernor. When present, ops.memory.get serves the real governance snapshot; absent it stays cataloged-but-unhandled. */ readonly memoryGovernor?: MemoryGatewayService | undefined; /** Optional: managed local-voice provisioning. When present, voice.local.status/install serve real state; absent they stay cataloged-but-unhandled. */ readonly voiceSetup?: VoiceSetupGatewayService | undefined; /** * Optional override for the calendar backend. Absent in the real daemon, * which composes the Google-backed one from its own daemon-tier config and * secrets (see routes/calendar-composition.ts); present in tests that serve * calendar.events.* / calendar.ics.* from a fake with no store behind it. * * A missing calendar implementation the daemon could reach, not a missing * route, is what made those five methods `invokable: false` for so long. */ readonly calendarGateway?: CalendarGatewayService | undefined; /** * Optional mail backend. When present, email.inbox.* / email.draft.create / * email.send serve real mail; absent they stay cataloged-but-unhandled. * * Its absence is why nothing the daemon did on its own could send a message. */ readonly emailGateway?: EmailGatewayService | undefined; /** Everything platform/email needs; absent in narrow compositions. */ readonly emailServiceDeps?: EmailCompositionDeps['emailServiceDeps']; /** * Why the mailbox is not usable yet, in the operator's own key names. * Supplied by a composition reading the daemon's `surfaces.email.*` keys. */ readonly describeEmailConfigProblem?: EmailCompositionDeps['describeEmailConfigProblem']; /** Operational log sink for the mail verbs; absent means they log nothing. */ readonly emailLog?: EmailCompositionDeps['emailLog']; /** * The following three are wired only by the full runtime-services composition * root; when any is absent (e.g. the terminal-shell embed) the proactive * check-in verb group is simply not registered, a graceful degrade, exactly * like the runtimeBus-gated needs-input push source above. */ readonly channelDeliveryRouter?: Pick | undefined; readonly providerRegistry?: ProviderRegistry | undefined; readonly automationManager?: Pick | undefined; /** A read-only session lister for the check-in briefing (the full SharedSessionBroker satisfies it). */ readonly sessionLister?: { listSessions(limit?: number): readonly CheckinSessionView[]; } | undefined; /** * The shared session broker's transport intake entry point. When present, the * inbound-intake enrichment (principal attribution + channel-profile * application) is installed on it so every channel-originated session is * enriched at submitMessage; absent → no enrichment is installed (graceful * degrade for embeds that wire no channel intake). */ readonly sessionIntake?: InboundIntakeBroker | undefined; /** Optional channel ingress-policy manager. Present: `sessionIntake` attributes a policy-authorized-owner sender to the owner principal instead of unknown (see `attributeInboundSession`). Absent: unchanged behavior. */ readonly channelPolicy?: Pick | undefined; /** * The daemon's working directory (source working tree). When present, the * worktrees.setup.run rerun verb is registered over a worktree registry rooted * here (the same store worktrees.snapshot reads); absent → the verb stays * cataloged-but-unhandled, a graceful degrade for embeds with no worktree root. */ readonly workingDirectory?: string | undefined; /** * Optional: receives the CI auto-watch tool-execution observer once the * ci-watch service exists, so the composition root can hang it on the shared * tool-execution seam, work pushed through the platform then mints its own * CI watch with no ceremony. Absent → only the scripted ci.watches.create. */ readonly onCiAutoWatch?: ((observer: (toolName: string, args: Record, success: boolean) => void) => void) | undefined; /** * Optional: a daemon-side conversation store port for the conversation half of * the unified rewind (rewind.plan/apply with scope 'conversation' or 'both'), * for sessions THIS process hosts the conversation for. * * It is no longer the only way that half gets served, and is no longer the * first one consulted. A surface running its own loop offers its live * conversation through the rewind.conversation.* verbs, and that offer wins * for its session, a process holding the messages is a better authority on * them than a store that merely might have them. This port is the fallback, * for sessions no surface has offered. * * With neither, the conversation half is reported unavailable with the reason * in a plan warning rather than faked. The files half is unaffected either way. */ readonly conversationRewindPort?: RewindConversationPort | null | undefined; /** * The relay WebAuthn step-up ceremony service. When present, the * stepup.credentials.register + stepup.challenge.mint verbs are registered over * it, the SAME instance whose verifier the relay dispatch gate installs * (services.ts constructs one and threads it to both). Absent (an embed with no * relay wiring) → the verbs stay cataloged-but-unhandled, a graceful degrade. */ readonly stepUpService?: StepUpGatewayService | undefined; /** * The canonical memory registry backing memory.projections.list/get. When * present, the read-only memory-projection verbs are registered over it; * absent (an embed with no memory store) → the verbs stay cataloged-but- * unhandled, a graceful degrade exactly like the other optional groups. */ readonly memoryRegistry?: MemoryProjectionSource | undefined; /** * The config + secret pair the credentials.set/.clear verbs write through, * the only way a surface that is not on this filesystem can finish a settings * modal that asks for a token. A SEPARATE bundle rather than a widening of * `configManager`/`secretsManager` above, because those two are deliberately * narrow Picks that several partial compositions satisfy today, and a * credential write needs members (`setDynamic`, `delete`) neither of them * declares. Absent → the verbs stay cataloged-but-unhandled, a graceful * degrade exactly like the other optional groups. */ readonly credentialWrites?: { readonly config: CredentialWriteConfig; readonly secrets: CredentialWriteSecrets; readonly additionalSecretKeys?: readonly string[] | undefined; } | undefined; /** * The broker a surface RAISES an ask into (approvals.raise). Separate from * `approvalBroker` above, which is the push fan-out's read-only * `ApprovalSource` view: raising needs the two members that view does not * declare. The real composition passes the same ApprovalBroker instance to * both. Absent → approvals.raise stays cataloged-but-unhandled, and the * decide verbs are unaffected. */ readonly approvalRaise?: ApprovalRaiseService | undefined; } export declare function registerGatewayVerbGroups(catalog: GatewayMethodCatalog, deps: GatewayVerbGroupDeps): void; //# sourceMappingURL=register-gateway-verb-groups.d.ts.map