26 Commits

Author SHA1 Message Date
jackson-jia-914 e9c1af03f6 feat(llm): add disableThinking option to suppress reasoning in extraction models (#228)
Support multiple inference engines and model providers via a strategy
enum so each receives its own thinking-disabling field in
chat-completion request bodies.

Strategies:
- "vllm"      → chat_template_kwargs: { enable_thinking: false }  (vLLM / SGLang)
- "deepseek"  → enable_thinking: false  (DeepSeek official API)
- "dashscope" → enable_thinking: false  (Alibaba DashScope / Qwen)
- "openai"    → reasoning_effort: "low"  (OpenAI o-series, cannot fully disable)
- "anthropic" → thinking: { type: "disabled" }  (Anthropic Claude)
- "kimi"      → thinking: { type: "disabled" }  (Kimi / Moonshot)
- "gemini"    → thinking_config: { thinking_budget: 0 }  (Google Gemini)

Defaults to false (no wrapper). Also accepts boolean true as a shorthand
for "vllm" for convenience.

Covered paths:
- StandaloneLLMRunner (OpenClaw plugin + gateway): llm.disableThinking,
  wired through parseConfig, tdai-core, seed-runtime, and gateway config
  (TDAI_LLM_DISABLE_THINKING env also accepts strategy names).
- Offload local mode (L1/L1.5/L2): separate offload.disableThinking.

The fetch wrapper lives in src/utils/no-think-fetch.ts with a
STRATEGY_TRANSFORMERS map for clean dispatch. StandaloneLLMRunner builds
it once in the constructor; LocalLlmClient caches it at construction
time and passes it through to callLlm().

Add vitest unit tests for all 7 strategies, normalization, validation,
embedding skip, and non-JSON tolerance (20 tests total).

Signed-off-by: jackson.jia <jiazhenghua0@gmail.com>
2026-06-24 18:02:25 +08:00
zhangxiaoshuai 38673b5d2b fix(embedding): enable retry backoff for embedding API calls (#173)
MAX_RETRIES was hardcoded to 0, making the existing exponential
backoff retry logic dead code. Transient errors (network jitter,
429 rate limits, DNS failures) caused immediate embedding failure,
contributing to JSONL/SQLite drift (issue #156).

Changed MAX_RETRIES from 0 to 3, matching the retry strategy
already used by tcvdb-client.ts.

Closes #159

Signed-off-by: WSL_zhangxiaoshuai <zhangxiaoshuai@wsl.com>
Co-authored-by: WSL_zhangxiaoshuai <zhangxiaoshuai@wsl.com>
2026-06-24 17:17:03 +08:00
Siren.W 9fac8bc998 fix(scene): adapt memory prompts to dialogue language (#147)
Signed-off-by: Siren.W <sirenexcelsior@gmail.com>
2026-06-24 17:04:01 +08:00
honchow a5a04c4007 Merge pull request #150 from MicroGery/fix/offload-auth-profile-key
[good first issue] fix(offload): resolve local-llm apiKey from auth-profile store as fallback
2026-06-15 11:23:43 +08:00
Ziyang Guo 823b478d46 fix(security): enable L1 prompt injection filtering (#175)
Signed-off-by: Ziyang Guo <121015044+RerankerGuo@users.noreply.github.com>
2026-06-15 01:06:26 +08:00
Willow Lopez f61c5fd5fd fix(pipeline): return cursor on zero-record L2 extraction for incremental processing (#130)
Fixes #98

Return { latestCursor: cursor } instead of void when VectorStore or
JSONL pipeline returns zero records. Add cold-start guard in pipeline
manager to initialize last_extraction_updated_time on first run.

Previously the L2 runner performed a full table scan on every invocation
because the cursor never advanced past the initial state.

Signed-off-by: Oxygen56 <1391083091@qq.com>
2026-06-15 01:04:52 +08:00
MicroGrey 093ddd293a test(offload): cover auth-profile apiKey resolver
Add unit tests for resolveApiKeyFromAuthProfile covering the four
resolution branches: api_key profile returns the plaintext key,
oauth/token-only providers return undefined, providers with no profile
return undefined, and keyRef-only (no plaintext) profiles are skipped.
Also covers the older-host case where the provider-auth SDK subpath is
unavailable.

To keep the test self-contained (no real OpenClaw install or on-disk
auth-profile store), resolveApiKeyFromAuthProfile gains an optional
fourth parameter to inject a fake provider-auth SDK loader. Production
callers pass three arguments and keep loading the real SDK via require.

Signed-off-by: MicroGrey <changyuhang@outlook.com>
2026-06-12 15:13:24 +08:00
fei121 b10a290ded [good first issue] fix: align config default comments (#174)
* fix: align config default comments

* Delete src/config.test.ts

---------

Co-authored-by: fei121 <fengyufei@fengyufeideMacBook-Pro.local>
Co-authored-by: 十五便士 <95488710+Maxwell-Code07@users.noreply.github.com>
2026-06-11 21:38:46 +08:00
MicroGrey a6fd25936c fix(offload): resolve local-llm apiKey from auth-profile store as fallback
When offload.model references a provider whose apiKey is managed via
OpenClaw's auth-profiles mechanism (the recommended default, e.g. set up
with 'openclaw auth'), the local-llm initializer previously only read
models.providers[provider].apiKey in the config tree. If the key was
absent there, LocalLlmClient construction was skipped and L1/L1.5/L2
were disabled with a misleading "missing apiKey" error -- even though the
key existed and the main agent model worked fine.

Fix: add src/offload/auth-profile-key.ts, a synchronous helper that
loads the auth-profile store via openclaw/plugin-sdk/provider-auth and
returns the first api_key-type credential for the provider. The helper
is guarded with try/catch so older OpenClaw versions that do not expose
this SDK subpath degrade silently to the previous behavior.

In index.ts, the apiKey lookup becomes:
  providerCfg?.apiKey ?? resolveApiKeyFromAuthProfile(api, providerKey, logger)

registerOffload() keeps its synchronous contract; no race is introduced
around backendClient assignment.

Closes #90
Signed-off-by: MicroGrey <changyuhang@outlook.com>
2026-06-04 21:36:48 +08:00
YOMXXX f92b10259b feat(embedding): support ZeroEntropy native embed API (#137)
* feat(embedding): support ZeroEntropy native embed API

ZeroEntropy's embedding service is reachable but not OpenAI-shaped:
its endpoint is POST /v1/models/embed (not /v1/embeddings), the
request body requires an `input_type` ("query" or "document") and
rejects an explicit `dimensions` field, and the response is wrapped
as `{results: [{embedding}]}` instead of OpenAI's
`{data: [{index, embedding}]}`.

Branch on `provider === "zeroentropy"` inside OpenAIEmbeddingService
rather than introducing a new service class:

- `/models/embed` is used instead of `/embeddings` (also honoured
  by the qclaw proxy path so users can still front it).
- request body sets `input_type: "query"` and omits `dimensions`
  (zembed-1 is fixed-dimension and rejects explicit overrides).
- response parsing decodes the `results[]` envelope and preserves
  input order via array index rather than OpenAI's `index` field.

`openclaw.plugin.json` updates the `embedding.provider` description to
list `zeroentropy` as a recognized value.

Everything else (timeout, retries, batching, char-cap truncation,
sanitize+normalize) is shared with the OpenAI path — only the wire
format changes.

Fixes #68

Signed-off-by: 李冠辰 <liguanchen@xiaomi.com>

* refactor(embedding): split ZeroEntropy into its own EmbeddingService

Addresses @Xuruida's review feedback on PR #137: instead of branching
OpenAIEmbeddingService._callApi on `providerName === "zeroentropy"`,
extract the shared HTTP layer and give ZeroEntropy its own class.

This keeps OpenAIEmbeddingService focused on the OpenAI-compatible
wire format and stops provider-specific protocol differences from
accumulating inside a single class.

Changes:

- New module-level helpers (provider-agnostic):
  * `truncateEmbeddingInputs(texts, maxInputChars, logger)` — was
    previously a private method duplicated logic-side.
  * `postEmbeddingRequest({fetchUrl, headers, body, timeoutMs})` —
    owns fetch + AbortController timeout + exponential-backoff
    retries + the EmbeddingApiError non-retry rule for 4xx (except
    429). Returns the parsed JSON; callers own response parsing.

- `OpenAIEmbeddingService._callApi` is back to OpenAI-only: builds
  `{input, model, dimensions?}`, posts to `/embeddings`, parses
  `{data: [{index, embedding}]}` with index-sort. The qclaw proxy
  branch is unchanged. No more `isZeroEntropy` reads.

- New `ZeroEntropyEmbeddingService implements EmbeddingService`:
  - Same `OpenAIEmbeddingConfig` shape (baseUrl / apiKey / model /
    dimensions / maxInputChars / timeoutMs are identical on the
    wire), but encodes ZeroEntropy's wire format independently:
    posts to `${baseUrl}/models/embed`, sends `input_type: "query"`,
    omits `dimensions` (zembed-1 is fixed-dim), parses
    `{results: [{embedding}]}` preserving array order.
  - Doesn't carry the OpenAI-only knobs (`sendDimensions`,
    `proxyUrl`, qclaw) so the class surface stays small.

- `createEmbeddingService` factory: new `provider === "zeroentropy"`
  branch placed before the OpenAI-compatible catch-all so it wins
  the dispatch.

Behaviour preserved:
- All existing OpenAI / DeepSeek / Azure / qclaw deployments hit
  exactly the same wire calls as before this PR was opened.
- ZeroEntropy wire format (URL + body + response) matches the
  original PR's branched implementation byte for byte.
- Timeout, retry, error mapping, batch splitting, char-cap
  truncation, sanitize+normalize are shared via the module helpers
  so the two services can't diverge by accident.

Signed-off-by: 李冠辰 <liguanchen@xiaomi.com>

* fix(embedding): forward dimensions to ZeroEntropy when sendDimensions

Addresses @Xuruida's follow-up review on PR #137. The previous commit
silently dropped `dimensions` from every ZeroEntropy request based on
the wrong claim that zembed-1 is fixed-dim.

Verified against
https://docs.zeroentropy.dev/api-reference/models/embed: `dimensions`
is an optional integer; for zembed-1 the accepted set is
[2560, 1280, 640, 320, 160, 80, 40] (Matryoshka truncation). Not
forwarding it left users no way to pick a smaller dim end-to-end —
the wire payload always used the model default while the Float32Array
return type and getDimensions() still reported config.dimensions, so
the runtime vector size and the configured one could disagree.

- ZeroEntropyEmbeddingService now mirrors OpenAIEmbeddingService:
  reads `config.sendDimensions ?? true` into a `sendDimensions` field
  and emits `dimensions: this.dims` in the request body when set.
- Class docstring corrected — the "rejects explicit overrides" line
  is replaced with the documented Matryoshka set. Server still
  rejects out-of-set values, but that's a config choice the user
  owns; we forward verbatim instead of clamping silently.

Signed-off-by: 李冠辰 <liguanchen@xiaomi.com>

---------

Signed-off-by: 李冠辰 <liguanchen@xiaomi.com>
2026-06-04 16:31:29 +08:00
Ruida Xu 32ba4d303d feat(timezone): make all user/LLM-facing timestamps timezone-configurable (#129)
Add top-level `timezone` config option (default: "system") supporting
IANA names and UTC offset strings (ECMA-402 2024).

- Introduce unified `src/utils/time.ts` module with formatForLLM,
  formatLocalDate, formatLocalDateTime, startOfLocalDay, nowInstantISO
- Converge 4 scattered time helpers into the single module
- L1 extraction / auto-recall / scene-extraction / persona-generation
  prompts now emit ISO 8601 with explicit UTC offset
- Add timezone declaration to LLM system prompts
- Local-day shard boundaries and cleaner cutoff follow configured tz
- Storage instants (SQLite/TCVDB) remain UTC — no data migration needed
- 38 unit tests covering IANA, offset, DST, half-hour zones, fallback
2026-06-04 11:44:01 +08:00
Akhilesh Arora c6ed755835 fix(recall): truncate recalled context by code point, not code unit (#109)
truncateRecallLine capped lines with line.length and line.slice, which
count and index by UTF-16 code unit. When recall.maxCharsPerMemory or
recall.maxTotalRecallChars is set and the cut lands between the halves of
a surrogate pair, the line keeps a lone surrogate that becomes U+FFFD once
UTF-8 encoded for the request, corrupting any non-BMP character (emoji,
CJK Ext-B) in the injected context. Slice on Array.from(line) code points
instead. Both recall call sites route through this function. Same class as
the sanitizeText fix in #31, reintroduced by the #71 budget path.

Signed-off-by: Akhilesh Arora <akhildawra@gmail.com>
2026-06-02 21:08:59 +08:00
Radian 3d588b0a0f Merge pull request #128 from withRiver/fix/remove-tools-clean-context-runner
fix: pass disableTools:true when enableTools=false in CleanContextRunner
2026-06-02 20:40:24 +08:00
Andy He d1534e488f refactor: unify duplicate Logger interfaces into single canonical type (#14)
* refactor: unify duplicate Logger interfaces into single canonical type

23 files had local Logger/StoreLogger/PluginLogger/PipelineLogger/RunnerLogger/ExtractorLogger/TriggerLogger
interface definitions with identical shapes ({debug?, info, warn, error}).

Replace all with imports from the canonical `Logger` in `src/core/types.ts`:
- 17 files: local `interface Logger` → `import type { Logger }`
- 6 files: named variants (StoreLogger, PluginLogger, etc.) → `type X = Logger` alias

Two intentionally different interfaces are preserved:
- `CheckpointLogger` (only info + optional warn) — different contract
- `ensure-hook-policy.ts` Logger (no error) — different contract

Net: -120 lines, zero behavioral change.

Signed-off-by: yuanrengu <heyonggang0811@126.com>

* docs: update Logger JSDoc to reflect canonical relationship

StoreLogger, PluginLogger, etc. are now type aliases of Logger, not the
other way around. Update the comment to clarify this inverted relationship.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: yuanrengu <heyonggang0811@126.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 15:12:46 +08:00
chrishuan 438869bec8 feat: release v0.3.6 2026-05-28 14:32:52 +08:00
YOMXXX 1bdcf28c5e feat(recall): cap injected memory context (#71)
- Add `recall.maxCharsPerMemory` and `recall.maxTotalRecallChars` with defaults of `0`, which do not alter existing behavior. Users can opt in by setting positive values to cap injected memory context.
- Apply the budget after L1 search and before `<relevant-memories>` injection, preserving score order while truncating oversized entries and dropping overflow.
- Document the new guards in README, README_CN, and `openclaw.plugin.json`.
2026-05-26 21:19:38 +08:00
Siren.W dc34ec5283 Do not expose tools for standalone text-only LLMRunner tasks (#59)
Removes tool exposure from the standalone `LLMRunner` when `enableTools=false`.

Some standalone text-only tasks, such as L1 extraction, expect plain text output and do not need tool access. Passing even a read-only tool subset can still encourage tool-calling behavior on OpenAI-compatible backends, which makes this path less stable than necessary.

With this change:

- `enableTools=true` keeps the existing sandboxed tool behavior
- `enableTools=false` sends no tools at all
2026-05-25 15:30:54 +08:00
李冠辰 49788fc085 fix(plugin): add OpenClaw state dir fallback 2026-05-25 00:10:57 +08:00
honchow bfddda6d3e Merge pull request #31 from akhilesharora/fix/sanitize-text-preserve-non-bmp
fix(offload): preserve non-BMP characters in sanitizeText
2026-05-21 14:46:19 +08:00
chrishuan 0d462e0b63 chore: release v0.3.5 2026-05-20 22:58:05 +08:00
Akhilesh Arora bf58853343 fix(offload): preserve non-BMP characters in sanitizeText
UNSAFE_CHAR_RE included the surrogate range [\uD800-\uDFFF] without the `u`
flag, so JS treated strings as UTF-16 code units and stripped each half of
every well-formed non-BMP code point. sanitizeText and sanitizeJsonLine
therefore destroyed emoji, CJK Extension B, math bold, etc. in tool params,
tool results, and ref-md archives.

Adding the `u` flag makes paired surrogates combine into a single code point
before matching, so the [\uD800-\uDFFF] entry now matches only lone (malformed)
surrogates, which is the original intent. All other entries (replacement char,
C0/C1 controls, zero-width chars, line separators, BOM) keep their behavior.

Added a vitest suite covering the preserved and stripped cases.

Closes #30
2026-05-16 22:23:45 +02:00
chrishuan d377b09fbc feat: release v0.3.4 — offload local LLM, CLI restore, bugfix scripts 2026-05-13 14:56:56 +08:00
chrishuan db8f3e516a feat: release v0.3.3 — Hermes adapter, context offload, core refactor 2026-05-13 01:58:18 +08:00
chrishuan a74b0b3e43 feat: release v0.2.2 — TCVDB backend, BM25 hybrid retrieval, pipeline refactor 2026-05-13 01:23:05 +08:00
chrishuan 76451838b8 feat: release v0.1.4 2026-04-23 10:59:54 +08:00
chrishuan 7a5fce9f12 feat: init Agent-Memory 2026-04-09 18:23:46 +08:00