dsh-web-search-ddg
aooyoo
Zero-token DuckDuckGo search provider for the DeepSeek Harness (DSH) web seam — local headless browser, no API key, no m…
PROJECT TOPICS
PROJECT README
Two-phase session captioning (automatic naming) for DeepSeek Harness (dsh): while a session is active, a title is generated instantly from keywords; once idle, the cheapest capable model is called to refine it. Everything runs in the background without interrupting the main flow — no wasted spend on titles.
user sends message ──► [Phase 1] instant keyword captioning (zero cost, millisecond)
│
└─► session idle for 5s ──► [Phase 2] budget-model refinement + one-line summary
│
└─► written to session/title (traceable)
New harness sessions are timestamp-numbered by default, making long session lists hard to scan. Existing approaches either only "wait for idle then generate" (no title while the user waits) or call the model every turn (expensive). This plugin splits the two ideas into complementary phases:
flash/haiku/mini etc.), with explicit model override and full phase-2 disabling both available.Fix Login Bug (2));session/caption-note event for list UIs, export tools, etc.;Install the latest version into a profile from GitHub:
dsh plugin --profile demo add github:JohnXu22786/session-titler
Remove it:
dsh plugin --profile demo remove dsh-session-caption
This plugin is a standard dsh bundle (configuration layer + plugin code), installed into a profile via dsh plugin:
# install from a local directory (development / self-use)
dsh plugin --profile demo add /path/to/dsh-session-caption
# or install a packed tarball (same for tarball / git references)
npm pack
dsh plugin --profile demo add ./dsh-session-caption-0.1.0.tgz
On install, pnpm links the package into the profile's node_modules; dsh recognizes the dsh.bundle declaration in package.json and adds the cordis.patch.yml layer to dsh.profile.bundles. Takes effect after restart:
dsh --profile demo --dump-config # should show the "session-caption" line
dsh
dsh.bundle.patch in package.json points to cordis.patch.yml — the only required bundle metadata;cordis.patch.yml first disables the built-in single-phase title provider by line id (the session title service accepts only one provider at a time), then inserts this plugin's configuration line;lib/src/index.js exports the standard Cordis plugin contract — name (session-caption), inject (['sessionTitle', 'sessions', 'llm']), Config (schemastery validation schema), apply(ctx, config);apply registers the two-phase flow as the sole provider of ctx.sessionTitle (automatic: 'all-user-messages'), and also listens to session/event and llm/adapters-updated; all listeners, timers, and registrations are reclaimed automatically on plugin unload.Note: the
session-titleservice is single-provider by design. If another plugin also registers a title provider, the two replace each other; this plugin's bundle layer disables the built-insession-title-llmline by default.
All fields are optional; defaults are listed below. Global config goes into $DSH_HOME/cordis.patch.yml, overridden by line id:
# $DSH_HOME/cordis.patch.yml (home-level, applies to all profiles)
- id: session-caption
config:
instant:
enabled: true # phase 1 switch
prefix: '' # instant caption prefix, e.g. '⚡ '
maxWords: 6 # max words for Latin titles
maxCjkChars: 14 # max characters for CJK titles
refine:
enabled: true # phase 2 switch
maxWords: 5 # target words for refined titles (Latin)
maxCjkChars: 10 # target characters for refined titles (CJK)
maxInputBytes: 4096 # byte cap for refine input messages (after JSON framing)
maxOutputTokens: 64 # token cap for refine output
timeoutMs: 60000 # per-call timeout for refinement
budget:
preferCheap: true # only pick from the low-cost model directory
# patterns: [...] # low-cost model name patterns (ordered by value)
summary:
enabled: true # summary switch
maxChars: 120 # max characters for the summary
timing:
idleDelayMs: 5000 # idle-detection delay (refine trigger point)
activityWindowMs: 1500 # activity window after an event
modelCacheMs: 120000 # budget routing cache duration
model:
provider: '' # explicit refine routing (paired with model)
model: '' # e.g. deepseek-official / deepseek-v4-flash
dedup:
enabled: true # title dedup (skip identical + cross-session numbering)
suffix: '({n})' # numbering suffix template, must contain {n} placeholder (from 2)
debug: false # debug logging
With
instant.enabledoff, phase 1 no longer produces keyword titles, but phase 2 still only runs in the idle window (auto-generation requests during busy periods are skipped; the title is generated by the timer once the session quiets down) — it never becomes "call the model on every message".
model.provider + model.model configuration;budget.preferCheap on, scan the model directories of all configurable providers, match by the pattern names in budget.patterns, and take the best (default order: flash → haiku → lite → mini → nano → fast → …; for the same tier, the shortest name wins), result cached for timing.modelCacheMs;request.route);Registered on ctx.sessionTitle, id session-caption, automatic mode all-user-messages:
| Field | Value | Description |
|---|---|---|
id |
session-caption |
source identifier written to session/title events |
automatic |
all-user-messages |
one generation per new user message |
generate(request) |
— | active → instant caption; idle → refinement |
generate receives { session, messages, route?, signal } and returns { title, messageSeqs, model? }. CaptionSkippedError is thrown in these cases (the service keeps the existing title, not treated as a failure):
source.kind === 'user');| Event | Type | Description |
|---|---|---|
session/title |
log-only (built into the harness) | snapshot of every accepted title, with source and message seq |
session/caption-note |
log-only (contributed by this plugin) | one-line summary on refinement: { title, note, messageSeqs } |
Like title, caption-note never enters the model context; replay tools that don't recognize the event can safely skip it (informational record).
src/
├── index.ts # plugin entry: name / inject / Config / apply
├── config.ts # config schema and runtime validation
├── context.ts # structured Harness context types
├── flow.ts # two-phase orchestration (generate / event feeding / dedup / summary)
├── keywords.ts # phase 1: keyword caption engine
├── refine.ts # phase 2: budget-model refinement + summary
├── budget.ts # cost routing: cheapest model selection and caching
├── pacemaker.ts # idle pacemaker: activity awareness + refine timing
├── normalizer.ts # title cleaning, length capping, comparison
├── language.ts # Latin/CJK script detection
├── events.ts # custom event declarations
└── errors.ts # CaptionSkippedError: skipped refinements
npm install # dev dependencies (incl. three forwarding packages under dev/pkgs, file: refs, reinstallable)
npm run typecheck # tsc type checking
npm test # vitest unit and flow tests (86 cases)
npm run build # compile to lib/src/
Runtime dependency note: at runtime the plugin uses
@deepseek-ai/dsh-llm,@deepseek-ai/dsh-session,@deepseek-ai/dsh-session-title, which are provided by the dsh installation itself and are not declared as dependencies/peerDependencies in the manifest — the npm transitive dependency chain of these three packages is currently incomplete (one transitive package is unpublished), and declaring them would break installation; if loaded into a custom profile lacking these packages, loading fails withERR_MODULE_NOT_FOUND— just install the package into the profile'snode_modules.Local dev mirror: the
stubs/directory is a minimal API mirror of these three packages (checked member-by-member against the released rc.1);dev/pkgs/holds three thin forwarding packages (file:deps) for tsc resolution and local tests; they are not shipped with the plugin (filesonly includeslib/src, the configuration layer, and docs).
MIT — see LICENSE.
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。