Modules — overview
Status: 優化中 / Tuning. APIs are stable; defaults and ergonomics are still settling as real apps stress-test them. Expect copy and default values to shift more than function signatures.
A dddk module is an opt-in, attachable bundle that adds one feature to a DotDotDuck instance without touching the orchestrator. Each follows the same shape:
import { SomeModule } from '@perhapxin/dddk';
const m = new SomeModule({ /* config */ });
m.attachTo(dddk);
// runtime toggles, mostly:
m.setEnabled(false);
m.destroy();
This pattern (rather than a giant new DotDotDuck({ inline: true, dwell: true, ... }) config) is deliberate:
- Independently toggleable. Hosts ship only the modules they actually use; the rest is tree-shaken.
- Self-owned lifecycle. Each module wires its own listeners, mounts its own DOM, and tears itself down. The orchestrator doesn't have to know.
- Replaceable per surface. A host that wants its own InlineAgent UI can write a sibling module against the same
DotDotDuckAPI surface, and the rest of dddk doesn't notice.
Modules are different from triggers (palette, Spotter, gestures — always present, always wired by the orchestrator) and skills (host-defined tasks the agent runs). Modules sit between: built-in features that hosts pick from a menu.
Naming convention: imports vs. displays
Module names appear in two forms depending on context:
- Class names (what you
import): PascalCase —ImmersiveTranslate,VoiceModule,InlineAgent,Dwell. - Palette commands / docs filenames: kebab-case —
/immersive-translate,voice,dwell,inline-agent.
When you read "the dwell module" in prose, it refers to the same thing as new Dwell(...) and /dwell — just three views of the same feature. The "Class" column in the table below is the canonical import name; everything else is a presentation alias.
The current modules
| File | Class | One-liner |
|---|---|---|
| voice | VoiceModule |
Hold-space STT, optional TTS, voice-cleanup LLM pass. |
| subtitle | Subtitle |
The bottom-of-screen prompt bar — agent narration, voice status, accept / reject. |
| dwell | Dwell |
Long-press to pin a DOM element as the next interaction's context. |
| inline-agent | InlineAgent |
Floating selection menu inside <input> / <textarea> / contenteditable. |
| immersive-translate | ImmersiveTranslate |
Page-wide bilingual rendering. |
| agent-cursor | AgentCursor |
Big visible cursor + pre-click pause while the agent is acting. |
| mobile-trigger | MobileTrigger |
FAB + swipe-in chrome bars for touch devices. |
| form-assist | FormAssistModule |
Inline "AI fill" button on each detected <form>. |
| onboarding | OnboardingModule |
First-visit ScriptSkill runner with persisted "seen" flag. |
| theme-language | ThemeToggleModule + LanguageSwitcherModule |
Two tiny utility modules grouped together — palette commands for theme and locale. |
Cross-tab session sync
Not a module — a DotDotDuck config flag. Listed here because it's a feature toggle and historically lived in this doc:
new DotDotDuck({
llm,
webAgent: { crossTabSync: true }, // default false
});
When true, the agent session is mirrored to localStorage and broadcast on a BroadcastChannel. A new tab on the same origin picks up where the previous one left off. Same-origin only — see agent / session-continuity.
Tear-down
When your app unmounts (page leave, host SPA route change) — always:
dotdotduck.destroy();
// + any modules you instantiated:
voice.destroy();
dwell.destroy();
inline.destroy();
immersive.disable(); // strips injected translations
agentCursor.destroy();
mobile.destroy();
Calling mount() again afterward without destroy will double-bind listeners.