Skip to content

chore(deps): bump actions/setup-python from 6 to 7 - #8

Open
dependabot[bot] wants to merge 89 commits into
mainfrom
dependabot/github_actions/actions/setup-python-7
Open

chore(deps): bump actions/setup-python from 6 to 7#8
dependabot[bot] wants to merge 89 commits into
mainfrom
dependabot/github_actions/actions/setup-python-7

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 25, 2026

Copy link
Copy Markdown

Bumps actions/setup-python from 6 to 7.

Release notes

Sourced from actions/setup-python's releases.

v7.0.0

What's Changed

Enhancements

Bug Fix

Dependency Upgrade

New Contributors

Full Changelog: actions/setup-python@v6...v7.0.0

v6.3.0

What's Changed

Enhancement

Dependency update

Documentation

New Contributors

Full Changelog: actions/setup-python@v6.2.0...v6.3.0

v6.2.0

What's Changed

Dependency Upgrades

... (truncated)

Commits

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Loop Memory Dev and others added 30 commits July 18, 2026 14:24
Replace the 8954-line vanilla JS single-page app with a Vue 3 (CDN, no
build step) component tree. Each pane is a self-contained ES module
imported directly by the browser, keeping the original <style>+<script>
delivery model so users still don't need npm.

Structure:
- serve/static/index.html     13-line shell that boots Vue and mounts App
- serve/static/css/app.css    extracted legacy stylesheet (99KB)
- serve/static/css/layout.css new app-shell + Dashboard + drawer styles
- serve/static/i18n/{en,zh}.json  472→517 keys (added 45 Vue-component keys)
- serve/static/js/main.js     createApp + mount entrypoint
- serve/static/js/App.js      top-level shell, owns polling, dispatches events
- serve/static/js/api.js      fetchJSON wrapper + domain endpoints
- serve/static/js/store.js    reactive global state (lang/theme/i18n/toasts)
- serve/static/js/components/  TopBar, Sidebar, Tabs, Timeline, Dashboard,
   Wiki, WikiEditor, KnowledgeGraph, Settings, RunStrip, Toast, Diagnostic

Bugfixes that shipped with the migration:
- Tabs nav visible (was crashing on .graph.split() because graph was 0)
- v-show drives tab visibility (was blocked by !important on .tab-pane)
- Drawer opens (was blocked by display:none on .drawer)
- llmConfig unwraps {config, warnings} envelope
- All Vue components' i18n keys present in en+zh

Dashboard now renders the full /api/insights payload:
- 10-tile KPI grid (total/today/active/links/entities/clusters/avg/decay/wiki/recall24h)
- 6-stage data-flow pipeline (extract→active→decayed→merged→archived→forgotten)
- Memory type and status distribution bars
- 24h ingest sparkline + per-source health
- Wiki health metrics + top compression candidates
- Animated live-status pulse dot

API-key UX: the input now shows a green '已配置 + fingerprint' pill when
a key is saved, so the cleared input field after Save is no longer
misleading. Tested: all 224 pytest cases pass; ruff clean.
The Wiki new-page button silently did nothing because Wiki.js referenced
<WikiEditor> in its template but never imported the component. Vue 3 then
treated the tag as a generic custom element and rendered an empty shell.

Fix: import { WikiEditor } from './WikiEditor.js' and register it in
the components: {} map. The new-page editor now opens correctly with
all form fields, and the test (createWiki / updateWiki / cancel) flow
works end-to-end.

Also switched Diagnostic's fetch from /api/health (404) to /api/diag
(200) so the modal now shows real subsystem status instead of an empty
error block.
- Dashboard.js: 10 KPI tiles + 3 ring meters + lifecycle + pulse +
  compression + granularity + distribution + sources + pipeline + health/weekly
  + LLM audit + write-guard + architecture loop. All sections guarded
  against missing fields so partial /api/insights still renders.
- Settings.js: remove stray </transition> closing tag that broke App
  template compilation, which prevented Dashboard's onMounted hook from
  firing on initial mount.
- layout.css: dashboard styles for KPI/rings/lifecycle/pulse/compression/
  granularity/distribution/sources/pipeline/health/audit/guard/arch.
… menu

KnowledgeGraph:
- Add mode selector (wiki vs memory) wired to /api/admin/graph/rebuild
- Add zoom +/- and Fit buttons in the toolbar
- Make the rotate toggle (⟳) toggleable
- Expand kind filter to all 7 kinds (concept, acronym, cjk, tag, url, path, wiki_page)
- Add side panel — title, kind badge, mention count, connected edges, evidence memories
- Add bottom legend strip (Wiki/Tag/Concept/Acronym)
- Add wiki_page dblclick → switch to Wiki tab and open that page
- Track mention count via api.graphEntityMemories helper

TopBar kebab menu: add Rebuild graph, Run doctor (Cmd+D), Consolidate items.
App.js wires the new events and listens for the global Cmd+D shortcut.
Wiki.js listens for loop-memory:open-wiki to open the matching editor.

i18n: action.consolidate, topbar.rebuildGraph, topbar.doctor, common.close
api.js: graphEntityMemories endpoint
app.css: stat-pill, graph-side, graph-legend, evidence-row, rel-row styles
layout.css: add 'display: flex; flex-direction: column' to .tab-panes so the
inner .tab-pane children size to the available viewport height. Without it,
the dashboard pane stretched to its natural content height (~5000px) and
overflow:auto never triggered a scrollbar.

store.js: smarter initial-language selection. Respect any explicit user
choice (localStorage), then fall back to navigator.language — zh-* locales
default to 'zh', en-* locales default to 'en', everything else falls back
to 'zh'. Browsers that report a non-zh locale no longer get an unwanted
English UI on first visit.
store.js: change _i18n from a plain object to a reactive({}) so the t()
function's dict lookups are tracked. loadI18n populates the dictionaries
via Object.assign on the reactive proxy, which fires per-key setters and
invalidates every computed that calls t() — fixing the 100-200ms window
during initial load where the tab labels and component text flashed raw
keys like 'tab.wiki' before the dictionaries arrived. Previously the
first render of every component that uses t() in its template would show
those raw keys until something else triggered a re-render (such as the
next stats poll), so on slow connections the wiki tab click could feel
unresponsive until a manual reload.

Wiki.js: watch store.activeTab. When the tab becomes 'wiki', call
refresh() so returning to the tab always shows fresh data (distillation
runs may have added/removed pages while the user was elsewhere). Falls
back to the onMounted-first-fetch path on the very first open.
Topbar (TopBar.js):
- Remove kebab three-dot overflow; promote Ingest / AI 整理 / 立即运行 into
  primary command group
- Add named '工具' dropdown caret instead of generic kebab, grouping
  maintenance actions (重打分 / 整理 / 重建图谱 / 运行 Doctor)
- Add language segmented control (中 / EN) and theme segmented control
  (A / ☀ / ☾) into a single utility row; settings cog remains

Dashboard (Dashboard.js + i18n):
- 统计概览 split into 4 primary KPIs + 7 compact KPIs; new side accent
  bar and dot-prefixed labels
- 生命周期 re-rendered as 6 icon stages with staggered 3-particle flow
  between gaps, breathing active-stage node
- 矛盾检测: clicking 合并 / 保留A / 保留B / 忽略 now removes the pair
  immediately (no double-click required) with toast feedback
- 衰减分数分布: rebuild SVG with 360x214 viewBox, grid lines, gradient,
  range labels below the bar, count value above; totals and peak range
  summary at the top
- Pulse titles: icon rendered via .label-icon so it does not double up
  with translated text emoji
- i18n: strip leading ⚠ 📊 🗜 🔬 📈 📡 📖 📥 🔍 ⇩ + duplicates from zh/en

Layout (layout.css):
- New topbar command group, tools dropdown, utility segmented controls
- Stat card tone-driven box-shadow, lifecycle stages/flow keyframes
- Decay chart SVG helpers, .resolving opacity for handled items
- Wiki masonry: columns: 3 320px with break-inside: avoid

224 tests still pass; ruff clean.
… restore lifecycle flow

Dashboard (Dashboard.js):
- refresh() no longer ends with await loadWeekly(): the weekly report is
  intentionally heavy and previously flickered its markdown on every
  6-second poll. Weekly is now only fetched:
    1. on initial mount (once)
    2. when the user clicks a different window pill (3d / 7d / 14d)
    3. when the user clicks the explicit \u21bb refresh button
- barsFor(): redesigned decay chart geometry (h 214 -> 230, plot.bottom
  38 -> 56). Returns shortLabel ('.1','.3',\u2026) + showShortLabel flag so
  the chart only renders every other axis label, eliminating the
  crowded-overlap problem at narrow widths. Adds a peak flag for the
  highest-count bar and a <title> tooltip carrying the full range + count.
- ingestBars(): 24 hourly buckets now expose tickY / lblY / tick / label
  so the template only paints major hour ticks (every 4h) plus a small
  idle-vs-active class plus a peak flag for the busy hour.
- ingestPeakHour() helper exposed to the template; renders a pill on
  the ingest card showing the peak hour and share of daily volume.
- Layout: 1.2 kB of template changes around the three charts above.
  Pulled <line> axis and per-bar <rect> tick into the SVG so the chart
  looks like an axis plot, not a tiny floating bar set.

Layout (layout.css):
- Decay chart: .ins-decay-axis, .ins-decay-tick, .ins-decay-bar.peak
  (with accent drop-shadow), and a slightly stronger .ins-decay-label
  opacity. Card SVG min-height bumped from 246 to keep proportions.
- Ingest chart: full set of .ins-ingest-* rules: grid lines, axis line,
  ticks, idle/peak bar states, hover brightness, .ins-src-peak pill.
- Lifecycle: connector pseudo-element switched from border-top dashed
  to a 12px segmented linear-gradient that animates background-position
  (lc-flow-dash 1.4s linear infinite) so the dashed line 'marching ants'
  toward the next stage. Particles bumped from 5 to 8 px with stacked
  glow shadows. Icon z-index raised to 3 and background set to opaque
  var(--surface) so the dashed line does not leak through.

224 tests still pass; ruff clean.
The contradiction 'merge' button previously just deleted the lower-scored
memory and surfaced a '已删除 1 条重复' toast. That contradicted the label
and surprised the user: '选择合并,为何也提示删除一条,不应该是两条
融合一下吗'.

Storage (sqlite_store.py):
- New merge_memories(a_id, b_id) method:
  * Winner = higher-scored side (ties go to A).
  * Loser's text is appended onto the winner with '\n\n---\n\n' as the
    separator, but the append is skipped when the loser's text is
    already a substring of the winner's (avoids duplication).
  * Winner's importance and score are bumped to the max of the pair so
    the fused memory carries the strongest signal.
  * Loser is deleted in the same transaction.
  * Pair is recorded in contradiction_ignored so it does not resurface.
  * Returns a small dict describing what changed (merged, kept, lost,
    appended, new_length, winner_was_a).

API (app.py):
- /api/contradictions/resolve action='merge' now calls merge_memories
  instead of doing a plain delete. Response shape moved from the old
  {deleted: [{id, kept}]} to {merged, winner, loser, appended, new_length}.
  Docstring updated to reflect true-fusion semantics.

Dashboard (Dashboard.js + i18n):
- Toast for 'merge' now reads '已合并两条记忆' / 'Merged both memories
  into one'. When the loser's text was actually appended, the toast
  also shows the new combined length ('已合并 · 新文本长度 N'). When only
  one side survived (the other was already gone) the toast says '已
  合并 · 一条记录已不存在'.
- keepA / keepB still delete the loser (single-side keep), toast text
  unchanged.

Tests (tests/test_contradictions.py):
- Replaced the old merge-then-delete test with three tests covering:
  * the standard fusion (winner keeps row, loser appended, max(score),
    pair hidden).
  * the no-op-append path when the loser's text is already inside the
    winner's.
  * the tie-keep-a path.
- 226 tests pass, ruff clean.
…imeline

User-visible bugs fixed:
- Topbar '导入' button did nothing: frontend posted to /api/ingest
  with source=manual, which 404s (the real route is
  /api/admin/ingest) and 'manual' is not a registered loader
  (loaders are codex/claude/hermes/openclaw). The action now opens a
  dedicated IngestPopover with a checkbox per loader; user can tick
  one or many and 'Ingest now' runs the per-source requests and shows
  the result per row.
- Same frontend/backend mismatch for rescore (/api/rescore vs
  /api/admin/rescore) and rebuildGraph (/api/graph/rebuild vs
  /api/admin/graph/rebuild). Both now go to the right URL. The
  stale /api/health alias is also replaced with /api/diag.
- Sidebar looked cluttered and unreadable: rewritten with source
  pills (All / Codex / OpenClaw / Claude / Hermes) at the top,
  per-session cards using a colored stripe + glyph icon,
  timeAgo reading ended_at (the field the API actually returns)
  instead of the missing last_seen, friendlier empty state, raised
  limit from 100 to 300 so Codex sessions are not hidden by heavier
  OpenClaw traffic, auto-refresh on the ingest event plus a 30s
  poller so new sessions show up without a manual reload.
- Clicking a sidebar session set activeSession but nothing read it,
  so the timeline ignored the user's choice. Timeline now watches
  store.activeSession and re-fetches with session_id; renders an
  accent scope banner with the session's title and a clear button;
  the recall path also respects the scope.

API / backend:
- /api/memories accepts session_id; works alongside source / kind /
  score / date range filters.
- /api/admin/ingest documented in popover tooltips.

UI / i18n:
- New IngestPopover component (Vue SFC inside the JS file) with
  per-source checkboxes, per-row result chip, run button, progress
  text. Goes under tb-ingest-trigger which now has a caret so the
  dropdown affordance is obvious.
- Timeline cards now expose .kind-lbl, .polish-spark, .dot-sep,
  .score-val, and a header summary chip + time range
  ('200 条记忆 · 2026/6/24 22:02:06 → 2026/7/18 20:24:51').
- zh/en i18n gain action.ingest* keys, sidebar.turns /
  filteringBySession / clearFilter / emptyHint, timeline.memories /
  sessionScope / clearScope / emptyForSession.

226 tests still pass; ruff clean.
- Add logo set (mark + light + horizontal lockup) using interlocking
  arcs around a memory node; reframe README + pyproject as a general
  agent memory system
- Lifecycle beads: fix the missing toneColor variable so the rolling
  colour-shifting balls actually render; rewrite the keyframes to
  use `left` (parent-relative) instead of `translateX %` (which
  referenced the ball's own width and never moved). Destination
  bead (p3) is ~1 size larger than the on-line beads and grows
  visibly when it lands at the next stage.
Clicking a session in the left rail used to only set the active-session
filter (which the Timeline already respected), but kept the user on
whichever tab was currently shown — so on Dashboard/Wiki/Graph users
saw no immediate effect. The session click now also flips
store.activeTab to 'timeline' so the right pane always opens to that
session's fragment list.

Tab↔URL sync moved into App.js (watch on store.activeTab) so any
writer — Tab clicks, sidebar session picks, Open-wiki from graph —
reaches the URL through one path. Removed the duplicate writer in
Tabs.js.
Two UI fixes:

1. The model-config entry in the topbar had background:transparent
   and border:1px solid transparent, so it dissolved into the chrome
   and was unfindable. Replaced the inline text+lock with a proper
   two-line chip (icon + provider + status badge). The badge reflects
   api_key_set in real time: green 'API key 已设' or amber 'API key
   缺失', so users get immediate visual confirmation that the key
   was saved (vs. the 'input cleared, looks unset' bug from earlier
   rounds). Clicking the chip still opens Settings.
2. Fit-to-window ('适配窗口') was firing correctly but produced a
   zero-pixel diff whenever the graph was already at optimum scale,
   so the click looked broken. Rewrote fit() to snapshot the current
   transform, compute the new fit, then tween scale/tx/ty over
   260ms with an ease-out-cubic. Initial loads and window-resize
   still use instant fit (correct UX for those), only the manual
   button click animates.
Three fixes on the dashboard 生命周期流转 strip:

1. **Centering**: rail vertical midline now matches icon vertical
   center. Was 10px below. New top:36px on a 32px icon placed in
   a stage with padding-top:22 → icon center at y=38, rail mid at
   y=38. Verified via headless screenshot.
2. **Slim the icons + beads**: icons 42→32px, heavy box-shadow halo
   replaced by a thin ring; remove the thumping keyframe animation
   and use a static 'active' glow. Beads shrunk from 6/5/12→4/3/6
   (still destination ~1.5× larger than on-line) and the heavy
   multi-layer glow collapsed into a single soft halo.
3. **Soften the color story**: rail opacity .55 → .45 with smoother
   fade-in/out at the icon ends (transparent → color → transparent);
   added a faint ::after blurred haze that suggests flow direction
   without competing with the beads. Bead keyframe nudged to fade
   in at 10% and pop at 85% (was 12%/88%) so the visual sits inside
   the rail, not at its edges. Animation slowed from 2.6s → 3.2s.
The previous fix tried to match the rail midline to the icon vertical
center but applied the math to the wrong coordinate space: the flow
is absolutely positioned inside `.ins-lc-stage` (not inside
`.ins-lifecycle`), so the lifecycle's 22px outer padding does not
shift it. The stage itself has padding-top:0, the icon is the first
flex child at y=0, icon height 32 → icon center y = 16.
Flow height 4 → flow top should be 16 - 2 = 14.

Headless measurement before this fix: flow cy = 786.5, icon cy = 764.5,
diff = +22px (the entire outer padding of .ins-lifecycle bleeding
through). After this fix: diff = 0px on all 5 visible stages.
The chat host returns 2049 'invalid api key' for keys issued against
the .com host, even though the docs and the platform.minimaxi.com
console both serve .com. Verified with the user's key:

  POST https://api.minimaxi.chat/v1/chat/completions
    → 401 {"status_code":2049,"status_msg":"invalid api key"}
  POST https://api.minimaxi.com/v1/chat/completions
    → 200 (works, returns proper chat completion)

Two call sites updated:
- PROVIDERS['MiniMax'].default_base_url
- build_provider() fallback when config has no base_url

Also migrated the saved user config (which had the old .chat URL
baked in from earlier sessions) via PUT /api/admin/llm/config so
the dashboard's '测试连接' button now passes against the .com host
without requiring the user to re-edit the base URL field.

Tests: 226/226 passing.
Symptom: user saved their MiniMax API key while the macos-keychain
backend was active, then restarted the server with the local-file
backend selected. The active store had no entry, the UI badge showed
'API key missing' and every LLM call went out without an Authorization
header → MiniMax returned 1004 'Please carry the API secret key'.

Fix: `get_secret(account)` now tries the active backend first; on
miss it asks each OS-native backend in turn (macos-keychain on Darwin,
linux-secret-service on Linux, windows-cred on Windows) and *migrates*
the value into the active store on hit. `has_secret` uses get_secret
so the UI badge becomes correct automatically. This keeps the
canonical store simple (one 0600 JSON file under ~/.loop_memory) while
never losing a key that was written by a different backend.

Verified: macOS keychain entry 'llm/minimax/api_key' was copied into
~/.loop_memory/secrets.json on the next config read; the '测试连接'
button now returns ok=true without re-pasting the key; all 226 tests
still pass.
Four user-visible issues fixed:

1. **Title** 'Settings' / 'LLM Consolidator Settings' → '大模型配置' /
   'LLM Configuration' (+ subtitle '一次配置,全局生效' /
   'Configure once, used everywhere'). The LLM is shared by every
   feature that needs one (consolidation, knowledge graph, weekly
   report, connectivity test) — the old name implied consolidation
   was the only consumer.

2. **Duplicate 'Run mode' label** in the schedule section. The
   inline label next to the 'enabled' checkbox was the same string
   as the label on the 'mode' dropdown. Renamed the first to
   '启用自动运行' / 'Enable auto-run' so the two controls are
   visually distinct (auto-run toggle vs. trigger mode picker).

3. **i18n rendering bug**: 'Next run: {when} ({mode}): Next run:
   7/19/2026, 3:00:00 AM (Daily)'. The template emitted
   't(settings.nextRun)' (literal template with unsubstituted
   {when}/{mode}) followed by ': ' followed by nextRunText()
   (already-substituted version). Fixed by emitting nextRunText()
   only. Replaced the gray plain-text line with a colored status
   pill (green when scheduled, gray when disabled) plus a per-mode
   hint line ('Every day at 3:00', 'Every Mon at 03:00', etc.).

4. **Scope clarity**: added a small gray note under each section
   header explaining what it covers. 'CONNECTION' lists the
   features that share this LLM; 'CONSOLIDATION SCHEDULE' and
   'CONSOLIDATION BEHAVIOUR' both say 'only affects consolidation'.

Touched files:
  - Settings.js: header, schedule label, nextRunText + scheduleModeHint, scope notes
  - app.css: .drawer-subtitle, .sec-scope, .sched-status pill
  - i18n/{en,zh}.json: 16 new keys (title, subtitle, sections, scopes, hints)

All 226 tests pass; no server-side changes needed (the config
storage key 'llm_consolidator' stays — every feature already
calls build_provider(cfg) on it, which is the existing global
config).
… settings button

Root cause of the stale 'API key missing' badge:

  scheduler.status() never returned api_key_set / last_test_ok /
  last_test_at, so App.js always saw api_key_set=false on boot and
  the chip said 'API key missing' even though the key was safely in
  the keychain.

Fix:

  Backend
  - scheduler._state: added last_test_ok / last_test_at /
    last_test_message + record_test_result() method.
  - scheduler.status(): reads api_key_set straight from the secret
    backend (so the chip is correct without any frontend round-trip),
    plus exposes last_test_ok/at so the chip can distinguish
    'verified reachable' from 'configured but unverified'.
  - handlers.llm_test(): optional scheduler param; records test
    result on success/failure (including URL errors).
  - _do_run_safe(): on a successful live run, promotes the dot to
    'verified reachable'; on an LLM-flavored exception, demotes it
    to 'unreachable' — so the chip stays honest without forcing the
    user to re-open Settings.
  - /api/admin/llm/test route: passes the scheduler through.

  Frontend
  - store.modelInfo: extended with reachability ('unset'|'ok'|
    'stale'|'fail') + last_test_ok/at/message.
  - App.refreshRunStatus: derives reachability from
    (api_key_set, last_test_ok, last_test_at) — green-pulsing for
    a recent success, green-static for set-but-unverified, amber
    for last-failed, red for unset.
  - TopBar model chip: redesigned to be a small pill (icon + model
    name + 8px dot). The wide 'API key 已配置 / 缺失' status text
    is gone — that info now lives only in the :title tooltip, so
    the topbar stays narrow.
  - TopBar settings button: the sun/gear SVG looked too much like
    the theme toggle (☀/☾), so users opened Settings by mistake.
    Replaced with a clean cog icon (8-tooth gear) clearly distinct
    from the theme controls.
  - i18n: 4 new tip templates (ok/stale/fail/unset) + 4 dot labels.

  CSS
  - .model-chip: compact 34px height, border tinted by reachability.
  - .model-chip .m-dot: 8px circle with halo + pulsing keyframe
    (2.2s ease-in-out, scale + expanding box-shadow).
  - Reduced-motion: disables the pulse for users who request it.

Verified screenshots (zh): ok=绿点脉动, fail=橙点, unset=红点;
all 226 tests pass.
Two issues fixed together:

1. **Two redundant buttons** in the topbar:
   - 'AI 整理' (llm-run) and '立即运行' (run-now) called the same
     backend endpoint, with the same payload; the 'force: true'
     flag on the second was silently dropped because the endpoint
     doesn't read it. Merged into one '立即整理' button. Dropped
     the now-dead onRunNow handler in App.js, the 'run-now' event
     in TopBar, and the obsolete llmRun/runNow i18n keys.

2. **Wiki knowledge base lacked import**:
   - GET /api/wiki/export already existed (markdown only); extended
     it with format=json so the export is round-trippable.
   - New POST /api/wiki/import: accepts either:
       * {format: 'json',     pages: [...]}  (round-trip)
       * {format: 'markdown', markdown: '...'}  (## title sections)
     Upserts by slug (creates new ones, updates matching slugs),
     returns {created, updated, skipped, errors, total}.
   - Wiki toolbar: added 导出全部 / 导入 buttons. Export triggers
     a JSON file download (timestamped). Import opens a file
     picker, infers format from filename + first char, POSTs to
     the endpoint, refreshes the page list, and shows a toast
     with the {created/updated/skipped} counts.
   - 4 new tests in tests/test_export_ask.py: format=json export,
     json import round-trip (create+update), markdown parsing of
     ## sections including Chinese titles, unknown-format rejection.

All 230 tests pass (was 226; +4 new import tests). Verified via
headless screenshot: topbar shows one '立即整理' button (no more
redundant pair); wiki toolbar shows 导出全部 / 导入 / 新建页面.
- _CLUSTER_SYSTEM / _WIKI_SYSTEM prompts drop hard char/bullet caps; new
  policy: every decision/number/name/constraint must survive intact
- Default token budgets raised: max_text_chars 1200->4000,
  max_output_tokens 800->4096, validator ceiling 4096->8192
- Per-call caps raised in evolution.py + llm_consolidate.py
- OpenAI/Anthropic adapter timeouts 20s->60s for longer outputs
- Wiki.js: bulletsOf no longer slices first 6; bodyPreview removed
- CONTRIBUTING.md rewritten (pytest, real layout, secrets, providers)
- docs/api.md: canonical HTTP API reference
- docs/providers.md: provider registration + key-storage story
- README adds docs nav table; CHANGELOG adds Unreleased entry

Verified: 230/230 tests pass; stage3+stage4 with new prompts produced
pages whose titles/summaries/bullets are no longer truncated.
A. Default watcher fix (治本):
  watcher.py: poll loop now tracks last_size_change_at instead of
  using st.st_mtime as the idle signal. Codex desktop (and similar
  agents) refresh mtime on background metadata flushes even when
  no new content is being written. Previously that kept resetting
  the idle timer for long active sessions so they never ingested.
  Size-stable-for-N-seconds is the correct signal.

B. Manual escape hatch (兜底):
  - loop-memory hook --once: process every eligible file once and
    exit. Used for ad-hoc batch runs without leaving a watcher.
  - loop-memory hook --idle SECONDS: override the default 60s idle.
  - watcher.run_once(): standalone single-pass helper used by both
    the CLI and the server endpoint.
  - POST /api/admin/watcher/force-ingest: in-process ingest with
    active_only=true|false to either pick the most-recently-modified
    transcript or run a full pass.
  - GET /api/admin/watcher/active-session: surfaces the live
    'currently active' transcript for the UI to display.
  - IngestPopover gets a top 'Force-ingest active session' panel:
    source selector + active-file preview + ⚡ button. Wired through
    the api.js client (forceIngest + activeSession helpers) and the
    i18n bundles (zh + en).

Verified end-to-end: clicking force-ingest against the live Codex
session writes the latest messages into the db (101 → 110 memories)
within a few seconds instead of waiting for the idle window.
The topbar button on the Wiki tab and the editor dialog header
both used wiki.new, which read '新建页面' / 'New page'. Renamed to
'新建知识' / 'New knowledge' per UX feedback — the tab is called
知识库 (knowledge base) so '新建知识' is consistent.

Also aligned the orphan wiki.newPage key (no current callers) so
the i18n bundle stays consistent and a future caller won't drift.
Per UX feedback, the Wiki tab is named 知识库 (knowledge base), so
its content units should be called 知识 (knowledge) throughout the
UI, not 页面 (page) or 词条 (entry).

Updated strings:
- New button / editor dialog: 新建知识 / New knowledge
- Empty state: 暂无知识 / No knowledge yet
- Confirmation: 删除这个知识? / Delete this knowledge?
- Toast: 已创建知识 / Knowledge created (also saved/deleted)
- Import/export tips: ...所有知识... / ...all knowledge...
- Import empty / no match: 未识别到任何知识 / No knowledge recognised
- Dashboard KPI labels: 条 (was 页) / knowledge items (was pages)
- Knowledge-graph node kind label: 知识 / Knowledge
- Model chip tooltips: 配置页 → 设置 (UI panel, not a 'page')

Did NOT change:
- DOM class names (.wiki-card-body etc) — they are CSS hooks
- API field names (wiki_pages) — those are server contracts
- i18n keys themselves (wiki.field.title, wiki.titlePlaceholder, etc)
The four toggles under 大模型配置 → 'AI 整理' (过滤噪音 / 重新打分 /
提炼合并 / 试运行) were rendering with each label as a full-width
block, stacking the span above the checkbox and producing ~60-70px
of vertical gap between rows.

Root cause: layout.css:129 forced every .drawer-body label to
display:block + margin-bottom:10px, and line 130 forced
.drawer-body label > span to display:block. Both rules are meant
for the input-form labels (batchSize, temperature, etc) but also
fired on the <label class='switch'> wrappers, breaking their
inline-flex layout.

Fix: dedicated .drawer-body .behaviour-switches styles that:
  - Lay the four switches out in a 2x2 grid (gap 8px row / 14px col)
    so the section is dense instead of running off the page
  - Override label.switch to inline-flex so the text sits next to
    the checkbox
  - Override the inner span to inline + restore normal sizing
  - Override the checkbox width:100% inherited from .drawer-body
    input so it stays a small native control
  - Add a subtle surface background + hover accent tint so each
    switch reads as a self-contained unit
Users reported that '新建页面' / 'New page' didn't update to
'新建知识' / 'New knowledge' even after a hard reload. Root cause:

  * Starlette's StaticFiles default sets Cache-Control: max-age=3600
    on every served file. Safari and Chrome both honour that for an
    hour after first hit.
  * The i18n JSON is fetched by store.js on app boot. With a 1-hour
    max-age the browser silently serves the stale en.json / zh.json
    and the new wiki.new='新建知识' value never reaches the Vue
    template.

Two-layer fix:

  1. Server-side middleware (serve/app.py:65) overrides
     Cache-Control per extension on /static/* responses:
       - json / html  -> no-cache, must-revalidate
       - js / css / fonts / images -> max-age=300, must-revalidate
       - everything else -> no-cache
     ETag/If-Modified-Since 304 still keeps unchanged files cheap.

  2. Client-side (store.js:83) adds { cache: 'no-store' } to the
     i18n fetch so even the very first hit after a deploy isn't
     poisoned by a previously cached response.

Verification:
  curl -sI :7767/static/i18n/zh.json -> no-cache, must-revalidate
  curl -sI :7767/static/js/store.js  -> max-age=300, must-revalidate
smartfind and others added 24 commits July 25, 2026 10:32
…ved)

Co-Authored-By: Claude <noreply@anthropic.com>
- Settings and Diagnostic are now defineAsyncComponent — their JS
  bundles are only fetched when the user first opens the modal
  (gear icon or Cmd+D).
- Dashboard, Wiki, KnowledgeGraph switched from v-show to v-if so
  they unmount completely when not active, freeing CPU/memory
  instead of just being hidden. Timeline stays v-show because it
  is the default tab and benefits from keeping state warm.
- WikiEditor now lazy-loaded inside Wiki — only fetched the first
  time the user clicks 'edit' on a page.
…uted helpers

Co-Authored-By: Claude <noreply@anthropic.com>
Also adds confirmClearTitle/confirmClearMsg i18n keys.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes #7 (CI lint failure).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Rename the Chinese dashboard tab from 看板 to 数据看板 for clarity
(distinguishes the metric dashboard from other dashboard-ish tabs).

Redesign the Settings drawer:

- Toggle rows now follow a single .row-toggle pattern across
  redact / storage / behaviour sections. Layout: label + description
  on the LEFT, checkbox on the RIGHT — descriptions always sit BELOW
  the label (never above).
- Storage section split into .settings-subsection groups
  (capacity / cadence). Auto-compact toggle now has a matching hint.
- Redaction section split into .settings-subsection groups
  (rule toggles / live preview). Visual rhythm matches storage.
- Behaviour section split into .settings-subsection groups
  (numeric parameters / processing toggles). All four switches
  (filter, score, summary, dry-run) gained explanatory hints.
- Added matching i18n hint keys to both en.json and zh.json.
- Unified the .row-toggle CSS into one rule shared by all three
  sections; pulled storage meter body into a single source of
  truth.
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code labels Jul 25, 2026
@smartfind
smartfind force-pushed the main branch 2 times, most recently from 0f8d541 to 24933b9 Compare July 29, 2026 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant