1. 26 Oct, 2026 11 commits
    • 郭阔's avatar
      fix(panel): prefer the freshest phase signal when heartbeat lags · 5b490bd9
      郭阔 authored
      The agent occasionally forgets to flip the .phase heartbeat on transition;
      when the objective disk evidence (registry/.summaries/.manifest mtime) is
      newer than the heartbeat file, trust the disk. Seen live: heartbeat stuck
      at 采集中 while manifest had landed 3 minutes earlier.
      5b490bd9
    • 郭阔's avatar
      feat(harvest-result): exhausted + all-invalid-candidates counts as success · d179c21f
      郭阔 authored
      Two validation refinements so 'nothing new but junk candidates' stops
      alarming as FAILED (reuters-markets case: 18 candidates, 16 deduped, the
      only 2 new ones rejected as non-article pages):
      - majority-failure rule downgrades to a warning when source_exhausted and
        every failure is a non-article rejection (og:type/body), same standing
        as all_seen
      - preloaded per-URL INVALID_OUTPUT failures no longer set task-failed
        (task-level no-URL failures and RUNTIME/PERMISSION still fatal); the
        per-article signal already lives in counts and warnings
      d179c21f
    • 郭阔's avatar
      feat(harvest): automatic retry + fallback provider for failed runs · 1a53ae75
      郭阔 authored
      attempt_needs_retry triggers on runtime error / empty output / non-object
      result (GLM StructuredOutput text-return case) / is_error (1301 etc);
      content-quality failures do NOT trigger (a provider switch won't fix them).
      Flow: attempt1 primary → attempt2 same provider (10s backoff) → attempt3
      FALLBACK_PROVIDER via resolve_continue_provider with its budget override
      (ccswitch-gpt=30). Idempotent end-to-end (7-day registry dedup + slug-keyed
      push), so re-runs only fill gaps; per-attempt raw outputs kept as *.attemptN.
      Verified decision matrix: success/runtime-error/invalid-output/api-error/
      business-fail.
      1a53ae75
    • 郭阔's avatar
      feat(panel): schedule board shows only enabled sources · 20ce81f8
      郭阔 authored
      Disabled/fully-off sources (skill off + schedule not active) no longer
      appear; transitional state (skill off but schedule still active) stays
      visible until reconciled.
      20ce81f8
    • 郭阔's avatar
    • 郭阔's avatar
      fix(panel): homepage status column shows schedule state in Chinese · 3256e4bb
      郭阔 authored
      systemd is-active semantics leaked to macOS (merged rows hardcoded
      active=unknown; scheduled rows showed systemd 'inactive'). Map every row
      to schedule_state: 调度中 / 已禁用 / 未排程.
      3256e4bb
    • 郭阔's avatar
      feat(panel): real-time phase from agent-written heartbeat file · beb80457
      郭阔 authored
      Replace guessed phases with ground truth: harvest.sh seeds
      logs/<source>.phase=采集中 and the prompt requires the agent to overwrite
      it the moment it enters each stage (采集中/摘要生成/推送/收尾); trap deletes
      it on exit. Panel reads the phase file first, falls back to factual disk
      signals (registry/.summaries/.manifest mtimes), and shows neutral 运行中
      when no signal exists — no more inference, no more (推定) labels.
      beb80457
    • 郭阔's avatar
      perf(harvest): fast-exit when all candidates already archived · 31945a77
      郭阔 authored
      Redundant runs (all articles deduped by the 7-day registry) burned ~19
      turns / $0.6 verifying nothing new. Prompt now instructs: when every
      candidate is already in the registry, return immediately with empty
      articles + source_exhausted=true + exhaustion_reason=all_seen — which the
      result contract already accepts as SUCCESS (exhaustion is not failure),
      also dodging the GLM StructuredOutput misreport for this case.
      31945a77
    • 郭阔's avatar
      fix(panel): phase inference fallback for dedup-only harvests · 902a7a36
      郭阔 authored
      A redundant run (all articles deduped) never rewrites .manifest.json, so
      the disk-signal inference stayed at 采集中 during the whole summary/finalize
      phase while the browser sat idle. After 10 minutes with no fetch signals,
      report 摘要生成(推定) instead.
      902a7a36
    • 郭阔's avatar
      fix(panel): running-harvest detection covers manually triggered runs · ed8738f2
      郭阔 authored
      active_harvests() only read launchd/systemd service state, so runs started
      via the panel button or a terminal (not launchd) were invisible on the
      homepage. Union with runlock-based detection: each port-<port>.<pid>.lock
      created by harvest.sh itself, PID liveness-checked, source name recovered
      from the PID's command line — launcher-independent.
      ed8738f2
    • 郭阔's avatar
      fix(harvest): runlock stale-PID cleanup misparsed lock filename · 3eadf69a
      郭阔 authored
      Lock files are port-<port>.<pid>.lock (two dots); ${f##*.} yielded the
      literal 'lock' instead of the PID, so the liveness check always failed
      and ANY exiting harvest deleted sibling LIVE locks — with all locks gone
      the last exiter then quit the site-shared browser out from under a
      still-running sibling harvest (nikkei-world 09:24 exit killed the
      browser mid nikkei-markets run; earlier 05:00 run likely degraded the
      same way until the 60min watchdog TERM'd it at exit=143).
      
      Parse correctly: basename minus .lock suffix, then last dot-segment.
      Self-test: live lock survives, dead-PID lock cleaned.
      3eadf69a
  2. 23 Oct, 2026 15 commits
    • 郭阔's avatar
      ui(panel): homepage drops 上次 column, keeps 下次执行 + 最近运行 · 1fdb4b7b
      郭阔 authored
      上次 trigger time duplicated 最近运行's log mtime — remove it (column now
      just 下次执行) along with its log-mtime backfill in index().
      1fdb4b7b
    • 郭阔's avatar
      fix(panel): homepage 上次触发 now shows real last-run time · 212c1a97
      郭阔 authored
      launchd exposes no last-trigger either (systemd-only) — fill it from the
      run log mtime (MM-DD HH:MM); merged unscheduled/disabled rows now carry
      their parse_last_log too, so only genuinely never-run sources show
      从未触发 (verified: 10 ran-today sources show real times, 4 never-run
      reuters sources remain 从未触发).
      212c1a97
    • 郭阔's avatar
      feat(panel): harvest phase display — homepage badge + detail timeline bar · bb648a72
      郭阔 authored
      - homepage: drop 成本/Session columns (detail-page info); running badge now
        shows inferred phase (采集中/摘要生成/推送收尾) via _harvest_phase():
        runlock mtime = start, vault-day .manifest/.summaries/.registry mtimes
        mark phase transitions — zero intrusion into the harvest process
      - detail page: timeline-style progress bar (采集中→摘要生成→推送/收尾→完成)
        with pulsing current-stage dot, green done segments, elapsed minutes,
        10s auto-refresh while running
      bb648a72
    • 郭阔's avatar
      feat(panel): homepage shows in-progress harvests · 3994a761
      郭阔 authored
      栏目列表 now surfaces running harvests: top banner (⟳ 正在采集: <sources>,
      browsers stay open until finish), per-row 运行中 badge above 最近运行,
      and 30s auto-refresh while any harvest is active — previously an
      in-flight run was invisible until its .last.log landed.
      3994a761
    • 郭阔's avatar
      feat(panel): homepage shows computed next-fire time · 910157b6
      郭阔 authored
      launchd exposes no next-trigger (systemd-only), so the 栏目列表 下次 column
      was always '-' on macOS — compute it from schedule times (same _next_fire
      as the schedule board) and highlight it amber.
      910157b6
    • 郭阔's avatar
      feat(panel): schedule board — 06:00 editorial deadline line · f4b55d54
      郭阔 authored
      Blue dashed deadline marker at 06:00 on every timeline (交付线 label) +
      intro copy; multi-time sources show 每日N次 badge. Editorial schedule
      applied: 02:30 nikkei-world / 03:00 reuters-business / 03:30 bloomberg /
      04:00 ft / 04:30 wsj / 05:00+17:00 nikkei-markets (12h interval), all
      finishing ~35min before the 06:00 deadline.
      f4b55d54
    • 郭阔's avatar
      feat(panel): configurable bind address for LAN access · 0c6460f5
      郭阔 authored
      app.py honors WEB_HOST/WEB_PORT env (default 127.0.0.1:8080); run-web.sh
      template exports them from web.conf — set WEB_HOST=0.0.0.0 to reach the
      panel from other systems on the same network (HTTP + Basic Auth, intranet
      use intended).
      0c6460f5
    • 郭阔's avatar
      feat(panel): schedule board — 24h timeline visualization + schedule CRUD · eb56fa8e
      郭阔 authored
      - new /schedules 调度中心: per-source row with a 24h timeline (green dots
        at fire times, next fire highlighted amber, red now-line), 定时/下次触发
        (countdown)/上次运行 columns, sorted by next fire, 60s auto-refresh
      - create/update: POST /schedule-time (HH:MM list, validated) →
        nh_schedule_write + enable (+skill enable for new rows)
      - delete: POST /schedule-delete/<name> → new platform.sh nh_schedule_remove
        (bootout + rm plist incl. disabled + entry script + per-source conf;
        skill source and history kept; digest/web protected)
      - guards: active harvests block time changes/deletes
      - nav link 调度中心; schedule_times() string parsed to list
      eb56fa8e
    • 郭阔's avatar
      docs(panel): rename per-source section to 验证登录 · 按源 · f0f61e25
      郭阔 authored
      Clearer purpose naming: the table's function is per-source login
      verification (test button + last result), not state management.
      f0f61e25
    • 郭阔's avatar
      feat(panel): manual login wizard per site · dc52b744
      郭阔 authored
      站点凭据表新增 人工登录 按钮:拉起站点共享实例窗口→用户在里面登录→
      后台 login-capture.js 每15秒自动检测(最长15分钟)→成功后记录登录态、
      缺配方站点自动生成检测配方并回收浏览器实例;页面自动刷新显示
      向导状态(运行中/登录已记录/未检测到)。复用 _login_test 轮询通道,
      键 manual:<site>。
      dc52b744
    • 郭阔's avatar
      fix(macOS): notify-mail empty-array under set -u; digest skip empty result JSON · 9be43efe
      郭阔 authored
      - notify-mail.sh: bash 3.2 (stock macOS) treats expanding an empty array
        under set -u as unbound variable — drop the MSMTP_ARGS array for direct
        branch invocation (works identically on Linux bash 5)
      - daily-digest.sh: empty result .json files (run killed / watchdog timeout,
        claude never wrote output) pass 'jq empty' but emit nothing on extraction,
        corrupting the summary array with consecutive commas; treat non-empty-size
        files as parse errors so killed runs show up honestly as interrupted
      9be43efe
    • 郭阔's avatar
      feat(panel): recycle browser instance after login test · 3b49d654
      郭阔 authored
      - platform.sh: new nh_quit_instance <source> — quits the site-shared instance
        after panel login tests; skips when a harvest holds the port runlock
      - app.py: login-test / login-test-src / login-discover background threads now
        recycle the instance when the test finishes (login state lives in profile,
        unaffected); manual-login flow (ensure-chromium --login) intentionally left open
      3b49d654
    • 郭阔's avatar
      feat(arch): site-shared browser profiles, auto-recycle and run watchdog · 155faf6a
      郭阔 authored
      - platform.sh: nh_ensure_instance/nh_entry_write now use per-SITE instance
        (port 9300+hash(site), profiles/<site>/) so one account logs in once per
        site; prevents same-account multi-login session kicking (digitimes/nikkei
        both enforce device limits)
      - harvest.sh: quit shared instance on EXIT with port-keyed reference counting
        ($WORK_DIR/runlocks, stale-PID cleanup) — last same-site run recycles
      - harvest.sh: hard watchdog kills a hung claude run after MAX_RUNTIME_MIN
        (default 60, per-source conf overridable) so launchd next-day trigger is
        never blocked
      155faf6a
    • 郭阔's avatar
      feat(panel): site-shared login model and list fixes · 12434efb
      郭阔 authored
      - index: unscheduled sources no longer link to /source (was 404); show 未排程 badge
      - index: sources with toggle ON sort first
      - logins page: split into per-source login-state table (site-shared instance
        column, per-source test via new /login-test-src route) + site-credential table
      - platform_layer: skill_sources() exposes method; login_sites() drops direct
        sources; new login_source_rows()
      12434efb
    • 郭阔's avatar
  3. 22 Oct, 2026 5 commits
    • 郭阔's avatar
      feat: recipe discovery for source-less sites · 7295d704
      郭阔 authored
      - platform.sh nh_login_autodiscover: no installed source for the site
        (bloomberg/statista) exports NH_LOGIN_URL from the credential's url
      - web: site rows without sources can run 自动配置配方/登录测试 (probe
        falls back to site name); credential url serves as discovery entry
      7295d704
    • 郭阔's avatar
      feat: site-level login credentials shared across channels · a17c0314
      郭阔 authored
      - platform.sh: credential lookup falls back site-level (logins/<site>.json,
        site = first segment of source name); nikkei-world/markets/tech share one
        nikkei account, single file to maintain
      - platform_layer: login_sites()/login_rows() site view (coverage, probe
        source, recipe status) for the panel
      - web: /logins page now site-based — save/delete/discover/test operate per
        site; discovery probes via the site's representative source
      a17c0314
    • 郭阔's avatar
      fix: login preflight auto-launches the source browser instance · acac322d
      郭阔 authored
      nh_login_preflight assumed the per-source Chromium was already running
      (panel login-test failed silently for sources whose window had been
      closed); extract nh_ensure_instance (no-op when alive) and use it in
      both preflight and autodiscover. Harvests were unaffected — harvest.js
      already runs ensure-chromium itself.
      acac322d
    • 郭阔's avatar
      feat: one-click recipe auto-discovery from the panel · 55ca6d6f
      郭阔 authored
      - nh_login_autodiscover (platform.sh): ensure the source's dedicated
        browser instance is up, load credentials, run skill discover
        (recipe generation + live login verification)
      - /login-discover route + 自动配置配方并测试 button for sources with
        credentials but no recipe; reuses the background-test polling UI
      55ca6d6f
    • 郭阔's avatar
      fix: login test output captures stderr too · 88a67a5f
      郭阔 authored
      88a67a5f
  4. 21 Oct, 2026 6 commits
    • 郭阔's avatar
      fix: remove last hardcoded user, panel refresh loop · 3e8496f6
      郭阔 authored
      - sudo -u yichu replaced by NH_RUN_USER env (linux panels running as
        root configure the execution user; empty = current user, darwin
        always direct) — last hardcode outside guarded legacy tools
      - logins.html: emit meta refresh only while a login test is running
        (content=0 caused an infinite reload loop)
      - deploy/migrate keep their overridable yichu defaults (legacy server
        tools, already parameterized via args/env)
      3e8496f6
    • 郭阔's avatar
      feat: web panel credential management for site logins · caf7cca6
      郭阔 authored
      - /logins page: per-source credentials CRUD (write-only password,
        masked display, 600-perm files in etc/news-harvester/logins/),
        recipe availability badge, one-click login test (background thread,
        page polls result)
      - nh_login_preflight auto-selects the source's dedicated browser
        instance (port+profile) when caller didn't set one
      - panel env carries SKILLS_DIR so recipe detection works in-process
      caf7cca6
    • 郭阔's avatar
      feat: login preflight before harvest · 6be2431a
      郭阔 authored
      - nh_login_preflight (platform.sh): sources with credentials at
        $CONF_DIR/logins/<source>.json (600) get a login check via the
        skill's login-tools.js before claude runs; auto-login on failure;
        harvest never blocked (paywall honestly reported by result contract)
      - harvest.sh invokes preflight after result contract preparation
      - SKILLS_DIR / SKILLS_REPO_URL documented in default.conf.example
      - test: preflight invoked when credentials configured (fake skill)
      6be2431a
    • 郭阔's avatar
      feat: per-source browser instance isolation · 776c3a9a
      郭阔 authored
      - nh_cdp_port: stable per-source CDP port (9300+name hash)
      - nh_entry_write: entry scripts now export CDP_PORT_OVERRIDE +
        CDP_PROFILE_OVERRIDE so parallel harvests each drive a dedicated
        Chromium (own port + own profile); regenerated idempotently
      - pairs with news-hub-skills CDP_PROFILE_OVERRIDE support
      - darwin test asserts isolation env in generated entry
      776c3a9a
    • 郭阔's avatar
      feat: setup.sh bootstrap and cross-platform web panel with harvest toggle · 5e5097d0
      郭阔 authored
      - bin/setup.sh: one-command machine bootstrap (auto platform detect) —
        directory layout, dependency check (brew install on consent), claude
        auth check, skills repo auto-clone from SKILLS_REPO_URL + npm install
        + full bundle import (all sources), dual skill symlink registration,
        default.conf/web.conf generation (random panel password), optional
        web panel service install, digest schedule, remaining-manual-steps
        summary; idempotent
      - web/app.py: platform_layer integration — paths/timer reads/trigger/
        active-harvest checks unified; add/edit/disable actions now work on
        darwin; per-source harvest toggle route (schedule load/unload +
        skill enable/disable; auto staggered schedule creation on first enable)
      - index.html: toggle switch column, unscheduled skill sources merged
        into list, platform-neutral labels
      - bin/platform_layer.py: python facade reusing platform.sh via
        'platform.sh call' dispatcher for all mutations; file reads native
      5e5097d0
    • 郭阔's avatar
      feat: platform abstraction layer (auto-detect darwin/linux) · 3ac999b0
      郭阔 authored
      - bin/platform.sh: single source of truth for platform differences —
        OS detection (NH_OS override for tests), PATH bootstrap without
        shadowing caller-injected commands, platform default paths, scheduler
        abstraction (systemd timer + systemctl <-> launchd plist + launchctl
        with named entry scripts), file-level disable on darwin (survives
        reboot, one-click recoverable), sed -i / GNU-sed shims, root check
        only on linux, conf loading (EnvironmentFile equivalent)
      - bin/schedule.py: plist parse/write + auto-detect by filename
        (timer/plist/.disabled); schedule sources listing subcommand
      - harvest/continue/daily-digest: drop hardcoded nvm//home/yichu paths,
        self-bootstrap via platform.sh, harvest now loads conf itself
        (run.sh wrapper no longer required)
      - add-source/modify-source: fully cross-platform schedule management
        (plist generation + launchctl on darwin, timer + systemctl on linux)
      - status.sh: cross-platform overview/detail/journal (launchd log on darwin)
      - deploy/migrate: linux-only guard with pointer to setup.sh
      - tests: env-synced platform simulation (NH_OS), fake launchctl suite,
        darwin plist/disable/enable roundtrip tests; new disable contract
        (unload + preserve times) replaces clear-times; 37 tests green
      3ac999b0
  5. 20 Oct, 2026 2 commits
    • 郭阔's avatar
      feat: macOS platform adaptation, concurrency guard and detail page redesign · ed91787c
      郭阔 authored
      - web/app.py: IS_DARWIN branches (launchctl kickstart for manual trigger,
        direct execution for digest/continue without sudo); _active_harvests()
        concurrency guard on /run and /continue; normalized detail state
        (last run time from log filename, next trigger computed from schedule)
      - detail.html: layout redesign — status badge header, real overview cards,
        merged duplicate run metadata, collapsible logs/config, history moved up
      - gitignore: exclude .zcode/
      ed91787c
    • 谢宇轩's avatar
      feat: new feature · 4e3e086a
      谢宇轩 authored
      4e3e086a
  6. 09 Sep, 2026 1 commit