Skip to content

feat(contexto): tighten public types + honor retrieval config#149

Open
Chen17-sq wants to merge 1 commit into
ekailabs:mainfrom
Chen17-sq:feat/133-tighten-types-and-honor-retrieval-config
Open

feat(contexto): tighten public types + honor retrieval config#149
Chen17-sq wants to merge 1 commit into
ekailabs:mainfrom
Chen17-sq:feat/133-tighten-types-and-honor-retrieval-config

Conversation

@Chen17-sq
Copy link
Copy Markdown

Linked issue

Closes #133.

Summary

Addresses both bullets the issue called out:

  1. Drop `any[]` from `SearchResult.items` — backend implementers ([contexto]: Implement LocalBackend #116) previously had to read `helpers.ts` to learn the item shape.
  2. Honor `maxResults` from config — `engine/base.ts` was passing the hardcoded `DEFAULT_MAX_RESULTS = 7` to the backend regardless of what callers set in `BaseConfig`. `minScore` was already wired through (line 89); `filter` was also wired but undocumented.

Plus a latent bug the type tightening surfaced (see below).

What changed

1. Public types in `src/types.ts`

Two new public types replace the `any[]` and mirror `@ekai/mindmap`'s `ConversationItem` / `ScoredItem`:

export interface MindmapItem {
  id: string;
  role: string;
  content: string;
  timestamp?: string;
  metadata?: Record<string, unknown>;
}

export interface ScoredMindmapItem {
  item: MindmapItem;
  score: number;
  estimatedTokens: number;
}

export interface SearchResult {
  items: ScoredMindmapItem[];   // was: any[]
  paths?: string[][];
}

Both are re-exported from the package entry. No runtime import added — the types describe a shape, not a binding.

2. Wire `maxResults` through

- const result = await this.backend.search(query, DEFAULT_MAX_RESULTS, filter, minScore);
+ const maxResults = this.config.maxResults ?? DEFAULT_MAX_RESULTS;
+ const result = await this.backend.search(query, maxResults, filter, minScore);

`BaseConfig` gains `maxResults?: number` alongside the (already-present) `minScore` and `filter`. All three are now also declared in:

  • `packages/contexto/openclaw.plugin.json` (runtime config schema)
  • `packages/contexto/src/index.ts` (inline plugin definition + the `register()` reader that threads them into the engine)

3. Latent bug fix (surfaced by type tightening)

`engine/base.ts` dedup loop:

// Before
const id = (r.item ?? r).id;  // r is ScoredMindmapItem — .id lives on r.item

// After
const id = r.item?.id;

Neither backend (local + remote) returns the raw-item shape — both wrap in `{item, score, ...}` and `MindmapItem.id` is required. The `?? r` fallback was unreachable; if it ever ran, `r.id` was `undefined` and silently disabled dedup.

4. Documentation

  • `packages/contexto/README.md` Configuration table gains rows for `contextEnabled`, `maxContextChars`, `maxResults`, `minScore`, `filter` (defaults inline).
  • `docs/contexto.md` gets a new "Tuning Retrieval" section pointing back to the README for the full list.

Diff scope

 docs/contexto.md                       | 12 ++++++++++++
 packages/contexto/README.md            |  5 +++++
 packages/contexto/openclaw.plugin.json | 14 ++++++++++++++
 packages/contexto/src/engine/base.ts   | 18 ++++++++++++------
 packages/contexto/src/index.ts         | 15 ++++++++++++++-
 packages/contexto/src/types.ts         | 33 ++++++++++++++++++++++++++++++++-
 6 files changed, 89 insertions(+), 8 deletions(-)

Backward compatibility

Zero behaviour change for existing users:

  • Adding optional fields to `BaseConfig` is non-breaking.
  • The `register()` reader uses `api.pluginConfig?.` which yields `undefined` for missing keys; `engine/base.ts` then falls back to the same defaults it was hardcoding before (`DEFAULT_MAX_RESULTS = 7`, `DEFAULT_MIN_SCORE = 0.45`).
  • `SearchResult.items` was `any[]` so any external code that destructured `item.content` / `item.id` / `item.metadata` still typechecks against `ScoredMindmapItem` (those fields exist on `MindmapItem`).

Test plan

  • `pnpm --filter @ekai/contexto run build` (tsc --noEmit): clean.
  • The latent-bug fix in `engine/base.ts` doesn't change runtime behaviour for the dominant path (where `r.item` is always populated) — it only makes the dedup work when an `{item: ..., score: ...}` shape comes back, which is the only shape the codebase actually produces.

The issue's "Tighten openclaw peerDependency version range" (#140) and "docs: Normalize docs..." (#135) are addressed in separate PRs (#147, #148) to keep diffs small per concern.

Two related changes that address both bullets of ekailabs#133:

# 1. Type the mindmap item shape (drop ``any[]``)

``SearchResult.items`` was ``any[]``. Anyone implementing
``ContextoBackend`` (ekailabs#116) had to read ``helpers.ts`` to learn the
field names. Replaced with two new public types that mirror
``@ekai/mindmap``'s ``ConversationItem`` / ``ScoredItem``:

  * ``MindmapItem`` — id, role, content, timestamp, metadata
  * ``ScoredMindmapItem`` — { item: MindmapItem, score, estimatedTokens }

Both are re-exported from ``packages/contexto/src/index.ts``. No
runtime import is added (the types live in the contexto package; the
mindmap shape they mirror is a contract, not an import).

The strict typing surfaced a latent bug in ``engine/base.ts``: the
dedup loop used ``(r.item ?? r).id``, but neither backend (local +
remote) returns raw items — both wrap in ``{item, score, ...}`` and
``MindmapItem.id`` is required. The ``?? r`` fallback was therefore
unreachable, and on the off chance it did execute, ``r.id`` would
have been ``undefined`` (silently disabling dedup). Replaced both
call sites with ``r.item?.id`` and left a comment explaining why.

# 2. Honor ``maxResults`` (was hardcoded) + document the defaults

``engine/base.ts`` was using ``DEFAULT_MAX_RESULTS = 7`` regardless
of what callers passed via ``BaseConfig``. ``minScore`` was already
wired through (line 89). Added ``maxResults?: number`` to BaseConfig
and threaded it through the search call. ``filter`` was also already
wired through but undocumented.

Configuration is now documented in three places, all consistent:

  * ``packages/contexto/openclaw.plugin.json`` — the runtime schema
    OpenClaw uses to validate plugin config; added entries for
    ``maxResults`` / ``minScore`` / ``filter``.
  * ``packages/contexto/src/index.ts`` — the inline ``configSchema``
    on the default-exported plugin definition; same three additions,
    plus the ``register()`` function now reads them from
    ``api.pluginConfig`` and threads them into ``base``.
  * ``packages/contexto/README.md`` Configuration table — gains rows
    for ``contextEnabled``, ``maxContextChars``, ``maxResults``,
    ``minScore``, ``filter`` with defaults called out inline.
  * ``docs/contexto.md`` — new "Tuning Retrieval" section that
    summarises the three knobs and points back to the README.

Closes ekailabs#133.

Test plan
---------
* ``pnpm --filter @ekai/contexto run build`` (tsc --noEmit) passes
  cleanly — the strict typing change fixed a latent bug in
  ``engine/base.ts`` that the loose ``any[]`` typing had been hiding.
* No runtime imports added; the new types reference an existing
  shape from ``@ekai/mindmap`` without depending on it at runtime.
* The ``register()`` reader in ``index.ts`` falls back to ``undefined``
  for missing fields, which then falls through to the engine's
  ``DEFAULT_MAX_RESULTS`` / ``DEFAULT_MIN_SCORE`` — existing users
  who don't set the new fields see zero behaviour change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[contexto]: Tighten public types and document retrieval defaults

1 participant