server

package
v0.0.0-...-cf4989e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 16, 2026 License: MIT Imports: 159 Imported by: 0

Documentation

Overview

Chat pipeline initialization: tool registration and handler construction. Extracted from registerSessionRPCMethods() to reduce that function to a clear sequential flow.

mail_autobackfill.go — one-shot startup seeding of the local mail store from the on-box IMAP archive.

The local mailstore (#3270) only receives NEW mail at runtime (LMTP intake + gmail-poll). Historical mail must be seeded, and that used to be a manual cmd/mail-backfill run only — so on a fresh store, mail_archive reads of older mail silently fell back to the ~12.9s per-call IMAP path (see the phase-timing log path=imap-fallback). This seeds the whole archive once in the background so those reads hit the fast local path instead.

One-shot via a sentinel file: it runs exactly once (not every boot), and independent of Len() — the store already holds recent mail, so an "empty store" guard would never fire on a live host. store.Put dedups by Message-ID, so the pass is idempotent and overlaps safely with live LMTP/gmail-poll writes.

memory_backup.go — wiring for the daily offsite memory backup task.

The agent's entire memory (wiki, diary, transcripts, polaris, workspace, contacts, kv) lives on the gateway host's single disk. The cluster's storage node is reachable over ssh only (its NFS export is mounted read-only here), so the backup streams a tar.gz through ssh. The task is registered only when this process owns the production state dir, keeping dev live-test instances from shipping duplicate archives.

memory_sweep.go — startup retention GC for memory stores.

Two stores accumulated files forever: the Polaris raw message store (860 files / 237MB observed in production, including a 107MB dead heartbeat file) and automated-session transcripts (1,400+ one-shot cron .jsonl files). Both sweeps run once at startup; the gateway restarts often, so a periodic timer would add nothing.

Centralized early-phase RPC method registration via GatewayHub.

Replaces the early register* wrappers with registerEarlyMethods for domains that do not need chatHandler. Late-phase wiring lives in method_registry_late.go and shared factories in method_registry_helpers.go.

Deps structs are assembled inline from hub accessors — no adapter layer. Handlers still accept their own Deps structs (testability preserved); only the method_registry files know about the hub→Deps mapping.

Package server implements the HTTP + SSE gateway server.

Handles health endpoints, SSE streams (chat/stream, events), RPC dispatch, OpenAI-compatible HTTP APIs, hooks webhooks, session management, and the miniapp client surface. Transport is SSE, not WebSocket.

Provider config loading, model/workspace resolution, and Gmail polling.

server_http_cors.go — CORS for the browser-based workstation client.

The native clients (Android/iOS) speak raw HTTP and don't enforce CORS, so the gateway never needed it. The Andromeda workstation, however, is a browser app (Tauri WebView2 in dev loads http://localhost:1420; the packaged app runs from the tauri://localhost origin). A browser there treats every call to the miniapp.* HTTP surface as cross-origin and blocks the response ("Failed to fetch") unless the gateway answers with CORS headers.

Auth is the custom X-Deneb-Client-Token header, never a cookie, which means:

  • the browser sends a CORS preflight (OPTIONS) before each call, and that preflight carries no token — so we must answer it here, ahead of the mux and nativeauth.Authenticate (which would otherwise 401 it), and
  • the actual request carries no ambient credentials. We echo the request Origin (with Vary: Origin) instead of "*": this is friendlier to future credentialed use and to caches, and is safe precisely because the token is an explicit header — reflecting an origin does not hand the secret to any site; a caller still needs the token to get past auth.

We deliberately do NOT set Access-Control-Allow-Credentials: the token is a header, not a cookie, so credentialed mode is unnecessary (and incompatible with reflecting arbitrary origins).

server_workstation.go — delivers workstation (desktop workspace) commands to connected Andromeda clients over the events push channel.

The chat tool `workstation` validates the verb (tools/runtimeops/ workstation.go); this side owns the transport: gate on a connected DESKTOP subscriber (a mobile-only connection must not read as "screen arranged"), then publish the command in the frame's Data under Kind="workspace". The desktop re-validates through its command bus (andromeda/src/commands.ts) and shows a visible "화면 조정" nudge, so machine-driven rearrangement is never silent. Fire-and-forget by design — a screen arrangement is idempotent and low-stakes, unlike phone actions there is no execution-result round trip.

session_labels.go — durable conversation titles across gateway restarts.

The session manager is pure in-memory and restarts are FREQUENT (auto-deploy hot-swaps the binary minutes after every landing). restoreAndWakeSessions rebuilds sessions by scanning the transcript dir, which dropped every Label — so the drawer's auto-generated conversation titles (chat.autoTitleSessionAsync, set-once) reverted to raw keys ("대화 · e6623080") on the next deploy, and old idle conversations never got re-titled.

Two pieces close the loop:

  1. A sidecar label store (~/.deneb/session-labels.json) — a periodic sweep snapshots {sessionKey → Label} for restorable sessions (write only on change) and a final flush runs on shutdown; the restore path re-applies stored labels.
  2. A one-shot backfill — restored sessions still missing a label get one derived from their transcript's first exchange via the same titler the live path uses (chat.GenerateSessionTitle, tiny role with heuristic fallback). After the first successful backfill the store carries every label, so later restarts make zero model calls.

sidecar_health_watch.go — wires sidecar dependency probes into the notify heartbeat (see runtime/notify/notify_deps.go for the alerting semantics).

Motivation (2026-07-17/18): the embedding sidecar (then BGE-M3) died cleanly and stayed down for 33 hours; the embedding client logged "server unhealthy" every batch but nothing reached the operator. This wiring closes that class: the heartbeat now watches every local dependency the gateway relies on.

wiki_approval_deal.go — files an analyzed 전자결재 document's approved cost onto the deal ledger (품목 단가 기억), the approval-side counterpart of wiki_mail_analysis.go's fileDealFromMail. Silent and best-effort: idempotent by docId, failures logged only — the analysis the user sees never depends on filing succeeding.

wiki_query_expander.go — tiny-role LLM expander for the wiki's vocabulary-gap backfill (domain/wiki/query_expansion.go). The expander is DORMANT unless DENEB_WIKI_QUERY_EXPANSION=backfill: the store only calls it when that gate is on AND a query under-filled its result limit, so wiring it unconditionally costs nothing at rest. Role, not model, is chosen here (model-roles rule) — tiny is the measured helper tier for small-budget rewrites.

workfeed_dream.go — surfaces a completed wiki dream cycle in the native work feed. The dreamer always wrote a proposal JSON to disk, but nothing user-facing showed that overnight consolidation happened; a compact card ("위키 드림: N 생성 · M 갱신") makes the autonomous work observable without a push notification (over-notification 금지 — the feed is pull).

Index

Constants

View Source
const DefaultTurnDeadline = 5 * time.Minute

DefaultTurnDeadline is the end-to-end budget for one user turn.

Variables

This section is empty.

Functions

This section is empty.

Types

type AutonomousSubsystem

type AutonomousSubsystem struct {
	// contains filtered or unexported fields
}

AutonomousSubsystem groups background/periodic services: the autonomous execution service, wiki dreamer, and Gmail polling service. All fields are late-bound during registerSessionRPCMethods() and registerWorkflowSideEffects(). Embedded in Server so fields are promoted and existing access patterns are unchanged.

type ChatManager

type ChatManager struct {
	// contains filtered or unexported fields
}

ChatManager groups the chat pipeline and its channel delivery backends. Embedded in Server so fields are promoted and existing access patterns are unchanged.

func (*ChatManager) ExternalMCPClient

func (m *ChatManager) ExternalMCPClient(name string) *mcpclient.Client

ExternalMCPClient returns the shared client for a configured external MCP server, or nil when that server is not configured. The client is safe for concurrent use; discovery state does not matter (calls lazily (re)spawn).

type GenesisSubsystem

type GenesisSubsystem struct {
	// contains filtered or unexported fields
}

GenesisSubsystem groups skill genesis services: the genesis service (auto-creation from sessions), usage tracker, skill evolver, and the iteration-based Nudger that fires mid-session skill reviews. Late-bound during registerWorkflowSideEffects() after the chat handler and LLM clients are available. Embedded in Server so fields are promoted.

Concrete leaf types (generation/review) are reached through skilllifecycle aliases so this composition-root package does not import those leaves.

type HookManager

type HookManager struct {
	// contains filtered or unexported fields
}

HookManager groups the scheduled-task (cron) subsystems. Embedded in Server so fields are promoted and existing access patterns are unchanged.

type InfraSubsystem

type InfraSubsystem struct {
	// contains filtered or unexported fields
}

InfraSubsystem groups infrastructure services with independent lifecycles (currently the maintenance runner). Embedded in Server so fields are promoted and existing access patterns are unchanged.

func NewInfraSubsystem

func NewInfraSubsystem(logger *slog.Logger, denebDir string) *InfraSubsystem

NewInfraSubsystem creates infrastructure services that can be eagerly initialized.

type MemorySubsystem

type MemorySubsystem struct {
	// contains filtered or unexported fields
}

MemorySubsystem groups the wiki knowledge base and contacts address-book mirror. wikiStore is late-bound during initMemorySubsystem() in the chat pipeline setup. contactsStore is created earlier, during registerEarlyMethods() (no chat dep), so it is available when the contacts tool is wired during chat init. Embedded in Server so fields are promoted and existing access patterns are unchanged.

type Option

type Option func(*Server)

Option configures the gateway server.

func WithConfig

func WithConfig(cfg *config.GatewayRuntimeConfig) Option

WithConfig sets the resolved runtime configuration.

func WithLogColor

func WithLogColor(color bool) Option

WithLogColor enables ANSI color in startup/shutdown banners.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets a custom logger.

func WithVersion

func WithVersion(version string) Option

WithVersion sets the server version string.

type Server

type Server struct {
	*ServerTransport
	*ServerRPC
	*ServerRuntime

	// Decomposed from ServerIntegrations — each independently constructable/testable.
	*WorkflowSubsystem
	*MemorySubsystem

	*AutonomousSubsystem
	*InfraSubsystem
	*GenesisSubsystem

	// Session, chat, and hook subsystems — logically grouped to reduce God-Object growth.
	*SessionManager // sessions, transcript
	*ChatManager    // chatHandler, toolDeps, modelRegistry
	*HookManager    // hooks, cron, cronRunLog

	// OnListening is called after the TCP listener is bound successfully.
	// Use this to print the startup banner or signal readiness to external callers.
	OnListening func(addr net.Addr)
	// contains filtered or unexported fields
}

Server is the main gateway server.

func New

func New(addr string, opts ...Option) (*Server, error)

New creates a new gateway server bound to the given address.

func (*Server) BoundAddr

func (s *Server) BoundAddr() string

BoundAddr returns the resolved listen address (e.g. "127.0.0.1:18789") once Run() has bound the listener, or "" before. Callers that depend on the address must tolerate the empty case during startup.

func (*Server) Broadcaster

func (s *Server) Broadcaster() *events.Broadcaster

Broadcaster returns the event broadcaster for external use.

func (*Server) Close

func (s *Server) Close(ctx context.Context) error

Close gracefully shuts down the server.

func (*Server) Publisher

func (s *Server) Publisher() *events.Publisher

Publisher returns the event publisher for enriched event delivery.

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run starts the server and blocks until the context is canceled.

func (*Server) SetDaemon

func (s *Server) SetDaemon(d *daemon.Daemon)

SetDaemon attaches the daemon used by server monitoring.

func (*Server) ShutdownCtx

func (s *Server) ShutdownCtx() context.Context

ShutdownCtx returns the server's lifecycle context, which is cancelled when doShutdown() runs. Background goroutines that outlive individual requests should derive from this so graceful shutdown does not leak them. Returns a non-nil context.Background before Run() has initialized the lifecycle context, so callers need not nil-check.

func (*Server) StartAndListen

func (s *Server) StartAndListen(ctx context.Context) (net.Addr, error)

StartAndListen starts the server and returns its actual address (useful with port ":0"). The caller must call Close() to stop the server; the serve goroutine is tied to the http.Server lifecycle and will exit when Shutdown is called.

func (*Server) StartMonitoring

func (s *Server) StartMonitoring(ctx context.Context)

StartMonitoring starts server health and lifecycle monitoring.

type ServerRPC

type ServerRPC struct {
	// contains filtered or unexported fields
}

ServerRPC owns dispatcher construction and RPC wiring state.

type ServerRuntime

type ServerRuntime struct {
	// contains filtered or unexported fields
}

ServerRuntime owns long-running runtime health/activity trackers.

type ServerTransport

type ServerTransport struct {
	// contains filtered or unexported fields
}

ServerTransport owns HTTP lifecycle and connection state.

type SessionManager

type SessionManager struct {
	// contains filtered or unexported fields
}

SessionManager groups session-lifecycle dependencies: the session store and autoreply session subsystems. Embedded in Server so fields are promoted and existing access patterns are unchanged.

type WorkflowSubsystem

type WorkflowSubsystem struct {
	// contains filtered or unexported fields
}

WorkflowSubsystem groups agent execution, approval, skill, and workflow domain stores. All fields are eagerly initialized and flow into GatewayHub for RPC handler wiring. Embedded in Server so fields are promoted and existing access patterns are unchanged.

func NewWorkflowSubsystem

func NewWorkflowSubsystem(logger *slog.Logger) *WorkflowSubsystem

NewWorkflowSubsystem creates all workflow domain stores. Every field is initialized eagerly; none require late-binding.

Source Files

Directories

Path Synopsis
Package toolbind holds concrete chat-tool bindings for the server composition root so internal/runtime/server does not import every tools/* leaf package.
Package toolbind holds concrete chat-tool bindings for the server composition root so internal/runtime/server does not import every tools/* leaf package.
observebind
Package observebind wires the concrete observe tool so toolbind's root package does not import runtimeops + observe leaf deps directly.
Package observebind wires the concrete observe tool so toolbind's root package does not import runtimeops + observe leaf deps directly.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL