Commit Graph
237 Commits
Author SHA1 Message Date
LeoandAgent Memory OS 674fc2af2a fix(charts): improve ranking labels and emoji cleanup (#225)
Co-authored-by: Agent Memory OS <jxy0811@users.noreply.github.com>
2026-06-15 10:42:01 +08:00
LeoandAgent Memory OS 208a125c48 fix(analysis): filter reply placeholders from catchphrases (#223)
Co-authored-by: Agent Memory OS <jxy0811@users.noreply.github.com>
2026-06-15 10:02:26 +08:00
Agent Memory OS ec2bb3d54b fix(wordcloud): filter media placeholders from text 2026-06-14 23:59:54 +08:00
diguaandClaude Sonnet 4.6 72f911a1cb fix(analytics): invalidate language-preference cache when Jieba dict changes
The language-preference endpoint calls createJiebaNlpProvider() which
uses the on-disk zh-CN dict. If the result was cached before the dict
was downloaded, the built-in fallback tokenization was served even after
the dict became available, until a DB write invalidated the cache.

Include the zh-CN dict file mtime/size as extraVersion in the cache
version so the entry is recomputed whenever the dict is added, replaced,
or deleted — consistent with the word-frequency fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:54:12 +08:00
diguaandClaude Sonnet 4.6 da2c48c801 fix(nlp): invalidate word-frequency cache when Jieba dict changes
When no dict was downloaded, word-frequency results were computed with
the built-in tokenizer and cached. Downloading a dict cleared the in-
memory Jieba instance but left the cached result unchanged, so subsequent
requests returned the lower-quality built-in tokenization until the chat
DB was modified.

Add an extraVersion option to withAnalyticsCache and pass the dict file's
mtime/size as extraVersion for word-frequency requests so the cache is
invalidated whenever the dict file is added, replaced, or deleted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:54:12 +08:00
diguaandClaude Sonnet 4.6 8a1ed8005c fix(analytics): invalidate time-sensitive cache entries daily
getDivingAnalysis and getNightOwlAnalysis derive values from the current
date (daysSinceLastMessage, currentStreak) that change even when the DB
file is untouched. Without a date component in the cache version, stale
results were returned until the next import or DB write.

Add a dailyInvalidate option to withAnalyticsCache that appends today's
date to the version string, and enable it for the diving and night-owl
endpoints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 23:54:12 +08:00
diguaandCursor 99ed14f8a5 refactor(analysis): add cached analytics endpoints for message & ranking charts
将 charts-message 的文字统计/长消息/月度趋势/文本长度百分位,以及 charts-ranking
的龙王/潜水/打卡/斗图/夜猫/复读六项分析从前端 pluginQuery+pluginCompute 移植到
@openchatlab/core,并通过 analytics 缓存路由暴露,消除前端拉全量数据再计算的开销。

- core: 新增 basic-queries 文本统计函数与 advanced/ranking.ts 六项分析及类型,含单测
- http-routes: 新增对应缓存路由,long-message-count 的 minLength 进缓存 key

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 23:54:12 +08:00
diguaandCursor 8192a43a41 refactor(electron): unify worker analytics cache onto file-version invalidation
The desktop worker kept a second analytics result cache in the same
cacheDir/query directory but invalidated it with a fragile scheme:
app-version .cache_version file, a dev-startup wipe, and explicit
invalidateAnalysisCache() calls after import. Replace it with the shared
getOrComputeAnalysisCache, fingerprinting entries by product version plus
the session DB file state, so the worker (still used by AI tools and the
desktop public REST API) and the shared HTTP routes share one cache and
one invalidation model. Fold the product version into the HTTP-route
fingerprint too, preserving the cross-release invalidation that
.cache_version used to provide, and drop the now-dead invalidation code.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 23:54:12 +08:00
diguaandCursor e87b359d23 perf(analysis): cache analytics results on disk keyed by db file version
Repeatedly switching time filters recomputed expensive analytics/NLP
queries from scratch (e.g. a 1-year word cloud took 10s+ after a detour
through 5-year), because cancelling the network request cannot stop the
synchronous server-side computation. Add a shared, platform-agnostic
disk cache under cacheDir/query, validated by a fingerprint derived from
the session DB file (mtime+size of the main file and its -wal sidecar) so
any import, incremental import or member edit transparently invalidates
it. Wire it through the shared HTTP routes (word-frequency plus all
analytics endpoints), so CLI Web and the desktop internal server share
one implementation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 23:54:12 +08:00
diguaandCursor 3bcdd3a2d4 perf(nlp): avoid re-segmentation when switching wordcloud word count
Switching the word count (80/100/150/200/300) in Insights > Topics >
Wordcloud re-ran a full-corpus jieba segmentation on the Electron main
thread, since topN only affects the final slice while the segmentation
cost is independent of it. This blocked the main process and froze the UI.

- Frontend: fetch the full word list once (max tier) and slice locally on
  word-count change, recomputing percentages over the displayed subset.
  Word-count switching no longer hits the backend or triggers segmentation.
- Backend: for Chinese meaningful/custom mode, produce word frequency and
  POS-tag stats in a single jieba.tag pass instead of two, halving CPU for
  genuine recomputes (filter/time/member changes). Behavior is preserved
  (verified by an equivalence regression test).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 23:54:12 +08:00
digua 615c0191b8 fix(database): repair missing segment message contexts 2026-06-14 23:54:12 +08:00
diguaandCursor 7afb31e679 fix(data): populate aiConversationCount from conversations.db instead of returning 0
The /_web/sessions route never called getAIChatCountsBySession() to
enrich session DTOs, leaving aiConversationCount always 0 in the
data management UI. Use the existing enrichSession callback and
AIChatManager to fill real counts for both list and detail endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 23:54:12 +08:00
diguaandClaude Sonnet 4.6 68cc5ffcee fix(owner): correct per-session ownerId cache and share one PreferencesManager
On name-match platforms (WhatsApp/Line/Instagram), applyProfileToOtherSessions
writes the local member's platformId to each session, which can differ from the
source ownerId. The session store was caching the source ownerId for all updated
sessions, causing owner display misalignment before a full reload.

Fix: applyProfileToOtherSessions now returns {id, ownerId}[] so the service can
expose updatedSessionOwnerIds in the result; the store uses that map instead of
the single source ownerId.

Also fix a stale-cache issue: registerSessionRoutes and registerPreferencesRoutes
each created their own PreferencesManager when ctx.preferencesManager was absent
(CLI and Electron both omit it). Two independent caches caused a later PATCH to
preferences to deep-merge against stale data and overwrite ownerProfilesByPlatform
back to its pre-selection value. registerSharedRoutes now creates one shared
instance and passes it to all routes via resolvedCtx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:16:52 +08:00
diguaandClaude Fable 5 82ce9dcc2f feat(owner): auto-apply owner profile after pull sync and CLI import
PullEngine gains an onSessionImported hook (hook errors never fail the
pull), wired in both the CLI server and Electron main to silently fill
session owner from the stored platform profile. The chatlab import CLI
command does the same after a successful import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 13:16:52 +08:00
diguaandClaude Fable 5 a75d17e310 feat(owner): add owner profile service and shared web routes
Shared node-runtime service writes session owner, maintains the
per-platform profile in preferences.json, and batch-applies it to
other unowned same-platform sessions (unique match only, never
overriding an existing owner). Exposed via /_web/sessions/:id/owner/
{apply-profile,select,dismiss-prompt} for both Electron and CLI Web.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 13:16:52 +08:00
diguaandClaude Fable 5 7589515eeb feat(owner): add platform-level owner profile types and matching
Add deterministic owner matching in core (exact platformId first, then
normalized-name fallback limited to whatsapp/line/instagram), plus
OwnerProfile preferences fields shared across Electron and CLI Web.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 13:16:52 +08:00
digua 82a6be4fcb fix(sync): delete imported session files completely 2026-06-13 13:16:52 +08:00
digua 56693685fa fix(ai): stabilize chart rendering during streaming 2026-06-12 11:36:46 +08:00
diguaandClaude Opus 4.8 bd1bc037b3 fix(ai): clarify last_message_time is data coverage end, not group activity time
AI was treating last_message_time as "when the group last had activity"
and drawing inferences like "the group has been quiet for two months,
maybe send a message to revive it." In reality, the timestamp only
reflects the end of imported data — the user may simply not have
imported newer records yet.

Add an inline parenthetical to the last_message_time field in all four
locale templates, and append a dedicated usage rule in
dataSnapshotUsageRules (zh-CN, en-US, zh-TW, ja-JP) explicitly
prohibiting inferences about group inactivity based on this value.
Also add the same note to formatDataSnapshotForPlanner's notes section
in data-snapshot.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 11:36:46 +08:00
diguaandClaude Opus 4.8 7316a09823 fix(ai): fix stale overview and analysis caches after incremental import
Overview cache had no self-invalidation: once written it would return
stale lastMessageTs forever if the postImportHook failed silently.
Analysis query cache (17 chart/stats queries) was never cleared on
import, so charts showed stale data until the user triggered a manual
invalidate.

Add MAX(message.id) fingerprint to OverviewCache and introduce
getValidatedOverviewCache(), which recomputes automatically when new
messages are inserted. Update resolveOverview() in the Electron worker
to use this instead of bare getCache().

In both postImportHooks (incrementalImport, streamImport) also call
deleteSessionCache against the query cache directory so chart data is
fresh after every import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 11:36:46 +08:00
diguaandClaude Sonnet 4.6 76acbabcc2 fix(ai): preserve tool-only turns in history replay and fix export regressions
- Include assistant messages with tool content blocks in getHistoryForAgent
  even when text content is empty, so multi-turn tool results are not lost
- Append Date.now() timestamp to export filenames to prevent silent overwrites
- Pass 600s timeout to exportFilterResultToFile worker call to match the
  actual upper bound of large-session exports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 22:18:20 +08:00
diguaandCursor 39700b95b7 feat(ai): display cache hit/miss token usage in chat status bar
Extend TokenUsage types across the full data pipeline to include
cacheReadTokens and cacheWriteTokens. The ChatStatusBar tooltip now
shows cache hit information when available. This is a generic
implementation that works with any API provider reporting cache metrics,
not limited to DeepSeek.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 22:18:20 +08:00
diguaandCursor 2b6aed52c9 fix(ai): replay reasoning_content for DeepSeek tool-call turns
DeepSeek API requires reasoning_content to be retransmitted on assistant
messages that precede tool results. Add ReplayOptions to history replay
so thinking blocks are included as ThinkingContent when the model uses
DeepSeek-format thinking, preventing 400 errors in multi-turn tool-call
conversations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 22:18:20 +08:00
digua 42649d835d fix(ai): count replayed tool results in compression token accounting 2026-06-10 22:18:20 +08:00
diguaandClaude Fable 5 9c7778f7e3 fix(ai): keep rawMessages out of LLM-facing tool result text
Message-retrieval tools mirror rawMessages inside data, and the
preprocessing pipeline rendered that array into the tool result text as
"rawMessages: [object Object], [object Object], ..." — roughly 1.7KB of
junk per call (~400-500 wasted tokens) that also polluted the truncated
result text persisted for history replay.

- formatToolResultAsText skips the rawMessages key (raw message objects
  are already rendered via the formatted messages list)
- CLI tool adapter strips rawMessages from extraDetails before invoking
  the pipeline, matching the existing desktop adapter behavior

Regression tests fail without the fix at both the format layer and the
CLI adapter layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 22:18:20 +08:00
diguaandClaude Fable 5 ad96dca7ac fix(ai): persist and replay tool calls in chat history
Multi-turn agent conversations rebuilt history as plain text, dropping
all tool calls and results. Later turns could not see what the model
had actually retrieved, so it imitated tool-free answers and
hallucinated chat records instead of re-querying (worse after
incremental imports and in long conversations).

- Persist provider-issued toolCallId, truncated result text and isError
  on tool content blocks (written by the shared frontend store from
  stream chunks; pi-agent-core events already carry these fields)
- Replay persisted tool blocks as real toolCall/toolResult message
  pairs in toPiHistoryMessages: stable ids keep requests byte-identical
  across turns (prompt cache friendly), unfinished blocks are skipped
  so no toolCall is left unanswered, errors map to isError, results are
  capped at 4000 chars, runtime-injected underscore params are stripped
- Legacy messages without persisted tool data fall back to the old
  plain-text replay; no migration needed

Verified on a real CLI web session: 25+ replayed tool pairs accepted by
the provider across turns with zero API errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 22:18:20 +08:00
digua 6ec736c05a feat(export): simplify message export and support multiple formats 2026-06-10 22:18:20 +08:00
diguaandClaude Sonnet 4.6 56ad4a9578 feat(ai): prefer high-level tools over raw SQL when rendering charts
Add optional `rows` parameter to render_chart so AI can pass pre-fetched
data arrays (e.g. from get_time_stats, member_stats) directly instead of
rewriting SQL from scratch. Update chart capability prompts and all locale
files to guide AI to check high-level tools first before falling back to SQL.
Remove debug console.debug log from agent-core thinkingLevel resolution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 00:15:25 +08:00
digua 1290ae6569 feat(ai): add chart auto mode preference 2026-06-10 00:15:25 +08:00
digua 84a3b13017 feat(ai): improve analysis and chart behavior 2026-06-10 00:15:25 +08:00
digua 6594e586d3 feat(ai): guide planner with startup context 2026-06-10 00:15:25 +08:00
digua 718735a748 feat(ai): build extended data snapshots 2026-06-10 00:15:25 +08:00
digua 383af0b13a feat(ai): enrich session data snapshot prompt 2026-06-10 00:15:25 +08:00
digua 0801ccf940 feat(ai): stream structured analysis plans 2026-06-10 00:15:25 +08:00
digua 9ad012680d feat(ai): integrate chart planning support 2026-06-10 00:15:25 +08:00
digua 6f54dcc62b feat(cli): expose full agent stream events 2026-06-10 00:15:25 +08:00
digua 2d413f4202 feat(ai): render analysis plan blocks 2026-06-10 00:15:25 +08:00
digua b61974136e feat(ai): emit analysis plan guidance 2026-06-10 00:15:25 +08:00
digua 645f0c6789 feat(ai): add analysis planner core 2026-06-10 00:15:25 +08:00
digua 8ca3f0be8b feat(ai): add llm fallback for shadow routing 2026-06-10 00:15:25 +08:00
digua 712d5212c2 feat(ai): log shadow agent route decisions 2026-06-10 00:15:25 +08:00
digua bd19512f92 test(ai): add agent routing evaluation set 2026-06-10 00:15:25 +08:00
diguaandClaude Opus 4.8 7af7fb1f10 fix: persist thinking level selection across restarts
thinkingLevels was excluded from the mergeDefaults whitelist in
PreferencesManager, so user choices were silently dropped on every
save/load cycle and never written to preferences.json.

Add the field to the Preferences interface, DEFAULTS, and mergeDefaults
so the existing backendPersist → savePreferences pipeline can write and
restore it correctly. Adds regression tests covering the save/load
round-trip, multi-save merge, and legacy-file backward compatibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:15:25 +08:00
Yui d70ab042ce fix(import): normalize jsonl timestamps for incremental import 2026-06-09 09:54:50 +08:00
diguaandClaude Sonnet 4.6 c3a5d22a43 fix: enforce data directory compatibility gate at runtime boundaries
MCP server checks the gate at startup and exits before opening stdio if
the data directory requires a newer runtime. Desktop HTTP API maps
DataDirCompatibilityError (including wrapped causes) to a 409 response
so the renderer can surface a clear upgrade prompt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 10:14:42 +08:00
diguaandClaude Sonnet 4.6 e7561e224e fix: write data directory compatibility gate on migration and import
Harden the startup migration path: fail fast and show an error dialog if
schema migration cannot complete. Repair the gate retroactively for
databases that were already migrated before the gate system existed.

Raise the gate after every import path (full import, incremental import,
stream import) and after opening any database that already carries a
current-segment schema. Centralise gate-raising inside DatabaseManager so
call sites cannot accidentally skip it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 10:14:42 +08:00
diguaandClaude Sonnet 4.6 1c7781d85b fix: establish runtime identity for data directory gates
Use the packaged app version (not Electron's dev-mode 0.0.0) across all
compatibility checks. Make DatabaseManager require a RuntimeIdentity so
every gate write carries a verified version and kind.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 10:14:42 +08:00
digua 6ae32e8b57 feat(http): report incompatible data directories 2026-06-08 10:14:42 +08:00
digua c518a4d968 feat(runtime): gate database access by data dir compatibility 2026-06-08 10:14:42 +08:00
digua c0885ba053 feat(runtime): add data directory compatibility gate 2026-06-08 10:14:42 +08:00