interface StorageAdapter { open(name: string): Promise; get(key: string): Promise; set(key: string, value: unknown): Promise; delete(key: string): Promise; iterate(prefix: string): AsyncIterable<[string, unknown]>; close(): Promise; } type StorageKind = 'memory' | 'indexeddb' | 'localstorage' | StorageAdapter; /** Memory v1 — three-tier hybrid memory for the webagent. */ type CoreFieldType = 'string' | 'array' | 'object' | 'number' | 'boolean'; type CoreMemorySchema = Record; interface CoreMemoryConfig { /** Field shape the LLM may write into. When omitted, free-form key-value. */ schema?: CoreMemorySchema; /** Where to keep the core block. Defaults to `localStorage`. */ storage?: 'localStorage' | 'sessionStorage' | 'memory'; /** Max stored byte budget. Writes that exceed are dropped + warned. */ maxBytes?: number; } interface EpisodicMemoryConfig { enabled?: boolean; /** Ring-buffer cap of stored session summaries. */ maxEntries?: number; /** How many summaries to load into the prompt per new run. */ replayTopK?: number; /** Selection heuristic. `keyword` does cheap token overlap; `recency` is order. */ relevance?: 'recency' | 'keyword'; storage?: 'localStorage' | 'sessionStorage' | 'memory'; } interface EpisodicEntry { id: string; summary: string; tags?: string[]; timestamp: number; turnCount?: number; taskSummary?: string; } interface SemanticMemoryEntry { id: string; content: string; tags?: string[]; metadata?: Record; timestamp: number; } /** Host-implemented backend for tier-3 semantic memory (vector store / * knowledge graph / whatever). dddk wires the agent's call site but does * NOT embed or persist — that's the host's job. */ interface MemoryProvider { remember(entry: Omit): Promise; recall(query: string, opts?: { topK?: number; tags?: string[]; }): Promise; forget(id: string): Promise; list?(opts?: { tags?: string[]; limit?: number; }): Promise; } interface SemanticMemoryConfig { provider: MemoryProvider; /** `explicit` — only writes when user marks important or the agent tool * call asks. `auto` — writes a candidate every turn (off by default). */ writeMode?: 'explicit' | 'auto'; } interface MemoryPrivacyConfig { /** Surface a one-off opt-in UI before the first write. */ requireConsent?: boolean; /** Cleanup horizon. Older episodic entries are dropped on next read. */ retentionDays?: number; /** Regex set that blocks matching text from being written. */ excludePatterns?: RegExp[]; } interface MemoryConfig { core?: CoreMemoryConfig; episodic?: EpisodicMemoryConfig; semantic?: SemanticMemoryConfig; privacy?: MemoryPrivacyConfig; } /** Tier 1 — structured user profile, always in context. < maxBytes. */ declare class CoreMemory { private data; private schema?; private storage; private maxBytes; constructor(cfg?: CoreMemoryConfig); private load; private persist; private validateField; get(key: string): unknown; set(key: string, value: unknown): boolean; remove(key: string): void; snapshot(): Record; clear(): void; /** Render as a system-prompt block. Empty string when no entries. */ toPromptBlock(): string; } /** Tier 2 — ring buffer of past session summaries. */ declare class EpisodicMemory { private entries; private maxEntries; private replayTopK; private relevance; private storage; private retentionDays; constructor(cfg?: EpisodicMemoryConfig, retentionDays?: number); private load; private persist; private pruneExpired; add(entry: Omit & { id?: string; timestamp?: number; }): EpisodicEntry; list(): EpisodicEntry[]; remove(id: string): void; topK(query: string | null, k: number): EpisodicEntry[]; clear(): void; /** Render as a system-prompt block. Empty string when no replays. */ toPromptBlock(query?: string | null): string; } /** Tier 3 — thin wrapper over a host-provided MemoryProvider. dddk does * NOT embed or persist; the host's provider owns infra. */ declare class SemanticMemory { private provider; readonly writeMode: 'explicit' | 'auto'; constructor(cfg: SemanticMemoryConfig); recall(query: string, opts?: { topK?: number; tags?: string[]; }): Promise; remember(entry: Omit): Promise; forget(id: string): Promise; list(opts?: { tags?: string[]; limit?: number; }): Promise; toPromptBlock(recalled: SemanticMemoryEntry[]): string; } type Drawer = { id: string; text: string; ts: number; wing?: string; room?: string; meta?: Record; }; type DrawerMemoryOpts = { customerId: string; storage?: StorageKind; /** * Optional host-supplied tokeniser. Receives the drawer text, returns * the feature list used for BM25 recall. Default handles 200+ languages * out of the box (no per-locale packs). See `dddk/utils/text`. */ extractFeatures?: (text: string) => string[]; sync?: { bootstrap?: () => Promise<{ drawers: Drawer[]; }>; pushChange?: (change: { op: 'add' | 'remove'; drawer?: Drawer; id?: string; }) => Promise; }; }; type DrawerSearchOpts = { topK?: number; scope?: { wing?: string; room?: string; }; recencyBoost?: boolean; }; declare class DrawerMemory { private customerId; private storage; private drawers; private features; private opened; private opts; private extract; constructor(opts: DrawerMemoryOpts); init(): Promise; private indexDrawer; wing(name: string): WingScope; addDrawer(d: Omit & { id?: string; }): Promise; removeDrawer(id: string): Promise; search(query: string, opts?: DrawerSearchOpts): Promise; recent(opts?: { wing?: string; room?: string; days?: number; limit?: number; }): Drawer[]; private persist; dispose(): Promise; } declare class WingScope { private mem; name: string; constructor(mem: DrawerMemory, name: string); room(name: string): RoomScope; search(q: string, opts?: DrawerSearchOpts): Promise; recent(opts?: { days?: number; limit?: number; }): Drawer[]; } declare class RoomScope { private mem; wing: string; room: string; constructor(mem: DrawerMemory, wing: string, room: string); addDrawer(d: { text: string; ts?: number; meta?: Record; }): Promise; search(q: string, opts?: DrawerSearchOpts): Promise; } /** Memory v1 — three-tier hybrid memory for the webagent. * * core — structured user profile, always in context * episodic — ring buffer of past session summaries * semantic — host-provided long-term recall (vector / KG / etc) * * All tiers are opt-in. A bare `new DotDotDuck({})` has memory disabled. * * The legacy verbatim-drawer store (formerly `Memory`) is still available * as `DrawerMemory` from `@perhapxin/dddk/agent` — they serve different * use cases and can coexist. */ declare class Memory { readonly core: CoreMemory | null; readonly episodic: EpisodicMemory | null; readonly semantic: SemanticMemory | null; private privacy; constructor(cfg?: MemoryConfig); /** Check if `text` would be blocked by PII patterns. */ isPII(text: string): boolean; /** Build the system-prompt context block for a new agent run. Runs * semantic recall against `userQuery` when wired. Empty string when * no tier has content. */ buildContext(userQuery: string): Promise; /** Called when a session ends. Writes a short episodic summary if the * tier is on. Caller supplies the summary text — hosts that want an * LLM-generated summary should produce it before calling. */ endSession(input: { summary: string; taskSummary?: string; tags?: string[]; turnCount?: number; }): EpisodicEntry | null; /** Wipe all tiers. */ clear(): void; } /** * Bridge between `Proactive` and dddk's PieceSurface render system. * * Usage: * const proactive = createProactive({ * render: createPieceRender({ mount: dddk.mountSurface, locale: 'zh-TW' }), * }); * * The host supplies a `mount` function — anything that takes a PieceSurface and resolves * the user's response ('yes' | 'no' | 'dismiss'). */ type PieceNode = { kind: string; text?: string; action?: string; shortcut?: string; children?: PieceNode[]; [k: string]: unknown; }; type PieceSurface = { root: PieceNode; data?: Record; }; type SurfaceMountFn = (surface: PieceSurface, opts: { placement?: 'inline' | 'dock' | 'modal'; timeoutMs?: number; }) => Promise<{ action: string; data?: Record; }>; declare function createPieceRender(opts: { mount: SurfaceMountFn; locale?: string; keys?: { yes?: string; no?: string; dismiss?: string; }; }): (prompt: PromptDefinition, surface: PromptSurface, ctx?: TriggerContext) => Promise<'yes' | 'no' | 'dismiss'>; /** Memory passed into trigger conditions — either the new 3-tier Memory * or the legacy DrawerMemory; triggers narrow as needed. */ type TriggerMemory = Memory | DrawerMemory; /** * Structural Analytics interface — the subset of `Analytics` that * proactive needs. Declared here (not imported from the analytics * module) so consumers passing a `dddk/modules/analytics` instance * aren't blocked by TypeScript's nominal-typing on private fields. */ interface AnalyticsLike { track(event: string, payload?: Record): void; } type Trigger = { id: string; condition: (ctx: TriggerContext) => boolean | Promise; }; type TriggerContext = { now: number; page?: string; dwellMs?: number; scrollDepth?: number; user?: Record; memory?: TriggerMemory; customMeta?: Record; }; type PromptSurface = { text: string | ((ctx: TriggerContext) => string); placement?: 'inline' | 'dock' | 'modal'; yesLabel?: string; noLabel?: string; dismissable?: boolean; autoTimeoutMs?: number; /** * Rich content surface. When provided, the renderer ignores `text` / * `yesLabel` / `noLabel` and renders this PieceSurface tree directly. * Use for recommendation grids (`OptionGroup` of `MediaCard`s), * confirm summaries, multi-option pickers — anything that benefits * from image + structured layout instead of plain text + yes/no. * * The piece tree should `trigger('choose', { value, index })` (or a * similar action) — the bridge maps any non-`yes` / non-`no` action * to `'dismiss'` for the standard 3-way response. Host can use the * `data` model to capture the picked value separately via analytics. */ pieces?: { /** Standard PieceSurface shape: { root: PieceNode, data?: object } */ root: { kind: string; [k: string]: unknown; }; data?: Record; }; }; type PromptDefinition = { id: string; category?: string; priority?: number; triggers: Trigger[]; triggerLogic?: 'AND' | 'OR'; surface: PromptSurface; onResponse?: (response: 'yes' | 'no' | 'dismiss', ctx: TriggerContext) => Promise | void; variants?: Array<{ id: string; surface: PromptSurface; }>; variantSelector?: 'random' | 'epsilon_greedy' | 'thompson_sampling'; }; type FatigueConfig = { maxPerSession?: number; cooldownMs?: number; dismissPenalty?: { sameId?: 'session' | number; sameCategory?: number; }; consecutiveDismissCap?: number; }; type ProactiveOpts = { analytics?: AnalyticsLike; memory?: TriggerMemory; storage?: StorageKind; fatigue?: FatigueConfig; keys?: { yes?: string; no?: string; dismiss?: string; }; identity?: () => Record; /** Locale for the default Yes / No prompt labels when a prompt's * surface doesn't supply its own. `en` / `zh-TW` ship bundled. */ locale?: string; render?: (prompt: PromptDefinition, variantSurface: PromptSurface) => Promise<'yes' | 'no' | 'dismiss'>; /** * Behavior when the dddk palette (or a PanelSkill) is currently open. * * - `suppress` (default) — do not trigger any proactive prompt * - `blur_palette` — visually blur/dim the palette and overlay * the proactive prompt (host renderer choice) * - `subtitle_only` — only show prompts via subtitle bar placement; * only Space counts as a response; any other * input is treated as "user didn't see it" * and palette flow continues unaffected. */ whenPaletteOpen?: 'suppress' | 'blur_palette' | 'subtitle_only'; /** * Callback that returns whether the palette is currently open. Wired by * dddk orchestrator (`isPaletteOpen()` / `isPanelOpen()`) so proactive can * respect palette state without a hard dependency on dddk's class. */ isPaletteOpen?: () => boolean; }; declare class Proactive { private prompts; private state; private storage; private analytics?; private memory?; private opts; private opened; private sessionShown; private sessionDismisses; private lastShownAt; private paused; constructor(opts?: ProactiveOpts); init(): Promise; register(p: PromptDefinition): void; unregister(id: string): void; pause(): void; resume(): void; /** Evaluate all prompts; fire the highest-priority eligible one. Returns the prompt id if fired. */ tick(ctx?: Omit): Promise; ask(opts: { text: string; yesLabel?: string; noLabel?: string; }): Promise<'yes' | 'no' | 'dismiss'>; explain(id: string): { promptId: string; lastShownAt: number; dismissedInSession: boolean; variantStats: Record; fatigue: { sessionShown: number; sessionDismisses: number; timeSinceLastShown: number; }; } | undefined; private selectVariant; private fire; dispose(): Promise; } declare const triggers: { dwell(opts: { ms: number; scope?: "page" | "session"; }): Trigger; idleTime(opts: { ms: number; }): Trigger; scrollDepth(opts: { percent: number; }): Trigger; pageMatch(opts: { path: RegExp | string; }): Trigger; exitIntent(): Trigger; schedule(opts: { everyN: number; }): Trigger; }; declare const builtin: { triggers: { dwell(opts: { ms: number; scope?: "page" | "session"; }): Trigger; idleTime(opts: { ms: number; }): Trigger; scrollDepth(opts: { percent: number; }): Trigger; pageMatch(opts: { path: RegExp | string; }): Trigger; exitIntent(): Trigger; schedule(opts: { everyN: number; }): Trigger; }; }; declare function createProactive(opts?: ProactiveOpts): Proactive; export { type AnalyticsLike, type FatigueConfig, type PieceNode, type PieceSurface, Proactive, type ProactiveOpts, type PromptDefinition, type PromptSurface, type SurfaceMountFn, type Trigger, type TriggerContext, type TriggerMemory, builtin, createPieceRender, createProactive, triggers };