docs(web): refresh v0.8.32 site state

This commit is contained in:
Hunter Bown
2026-05-12 14:04:17 -05:00
parent b25450728e
commit 190eb6b162
10 changed files with 155 additions and 264 deletions
+76 -97
View File
@@ -225,106 +225,85 @@ deepseek --provider ollama --model deepseek-coder:1.3b
--- ---
## What's New In v0.8.29 ## What's New In v0.8.32
A maintenance release anchored by a v0.8.27 / v0.8.28 regression fix A "more useful tools" release expanding the tool surface for real-world
plus 25 community PRs. [Full changelog](CHANGELOG.md). workflows. Five new tools, ten community PRs targeting model-protocol bugs
and UX papercuts, and a snapshot cap that stops giant workspaces from
hanging the TUI on first turn. [Full changelog](CHANGELOG.md).
- **Scroll demon, gone for good** (#1085 regression). Parallel sub- - **Five new tools.** `read_file` now extracts PDFs in pure Rust — no
agents running `exec_shell` would scroll the alt-screen out from Poppler install required. `pandoc_convert` moves documents between 11
under ratatui's diff renderer, leaving a blank band growing above formats (Markdown, HTML, DOCX, EPUB, LaTeX…). `image_ocr` runs local
the header. Three layers of defence now: a `tracing-subscriber` tesseract on screenshots and scanned documents. `image_analyze` sends
writing to `~/.deepseek/logs/tui-YYYY-MM-DD.log`, an fd-level images to a vision model for natural-language description (opt-in only).
`dup2` stderr redirect for the alt-screen lifetime (Unix), and `js_execution` mirrors `code_execution` for Node.js snippets.
module-level `#![deny(clippy::print_stdout, clippy::print_stderr)]` - **Two more providers.** AtlasCloud joins as a first-class provider
on the TUI runtime modules. New `eprintln!`s inside `tools/`, (`provider = "atlascloud"`) with the same config-surface shape as the
`core/`, `tui/`, `network_policy.rs`, or `runtime_threads.rs` now existing NVIDIA NIM / Fireworks rows. `web_search` supports Tavily and
fail CI. Bocha as configurable backends for regions where DuckDuckGo is
- **Ctrl+R session restore is workspace-scoped** (#1395, PR #1397 from unreliable.
**@linzhiqin2003**) — previously listed every saved session on disk, - **Prompt-cache survives mid-session edits** (PR #1345 from
which meant Project A's history could leak into Project B. **@Duducoco**). Moving `instructions`, user memory, and session goal
- **Runtime version visible in the header.** A discreet `v0.8.29` below the volatile-content boundary means the KV prefix cache no longer
chip sits in the header's right cluster alongside the provider / breaks every time you edit your memory file — skills and context
effort / Live / context chips. Drops first under tight terminal management instructions stay hot regardless of how often you run
width. `/memory`.
- **MCP HTTP transport honors HTTP(S)_PROXY** (#1408 from - **vLLM thinking toggle actually works now** (PR #1480 from
**@hlx98007**) — corporate / Clash / Shadowsocks proxies now apply **@h3c-hexin**). `reasoning_effort = "off"` on vLLM providers now emits
to MCP HTTP connections, matching every other tool on the box. the OpenAI `chat_template_kwargs.enable_thinking` extension instead of
`NO_PROXY` honored. the silently-ignored Anthropic-native field. Measured improvement on
- **MCP discovery survives malformed items** (#1410 from Qwen3: TTFT from ~13s → ~270ms.
**@Liu-Vince**) — one bad tool / resource / prompt entry no - **Kitty keyboard protocol on Windows** (PR #1483 from
longer drops the whole page; the malformed entry is skipped and **@CrepuscularIRIS / autoghclaw**). `Shift+Enter` now inserts a
the rest of the catalogue surfaces normally. newline instead of submitting in VSCode and Windows Terminal —
- **MCP SSE accepts CRLF-framed endpoint events** (#1309, PR #1358 previously indistinguishable from plain Enter on Windows.
from **@reidliu41**) — FastMCP / uvicorn streams no longer time - **Tool-result retrieval namespace unified** (#1541). Wire-dedup refs
out waiting for LF-only event separators. and disk-spillover refs now share a lookup path — `retrieve_tool_result`
- **Composer ignores leaked mouse-report bytes** (#1418, PR #1421 accepts SHA refs, bare hex hashes, `art_<id>` aliases, and absolute
from **@reidliu41**) — terminal chains that leak `[<35;44;18M` paths, with error messages that list every accepted form.
style mouse reports into stdin no longer fill the input area. - **Snapshots skip giant workspaces** (#1552). A 2 GB ceiling on
- **Footer chips respect the available width** (#1357, PR #1417 from non-excluded workspace content prevents first-turn `git add -A` from
**@Wenjunyun123**) — long cache / aux chips drop before crowding hanging the TUI on multi-hundred-GB project directories. Configurable
the left status line or composer area on narrow terminals. via `[snapshots] max_workspace_gb`; set to `0` to restore unbounded
- **Note management commands** (PR #1407 from **@reidliu41**) — behaviour.
`/note add`, `/note list`, and friends for persistent maintainer - **`deepseek update` refreshes both binaries** (PR #1492 from
notes inside the TUI. **@NorethSea**). The updater now enumerates colocated binaries (both
- **`/init`-style global AGENTS.md merges with project AGENTS.md** the dispatcher and the TUI runtime), downloads and verifies every
(#1157, PR #1399 from **@linzhiqin2003**) — your `~/.deepseek/ release asset, and writes the sibling first so a partial failure can't
AGENTS.md` baseline now layers under the workspace's own leave the launcher updated while the TUI stays stale.
AGENTS.md instead of being shadowed. - **Approval modal collapses to a one-line banner** (PR #1455 from
- **Language directive: thinking matches the user's message language** **@tiger-dog**). Tab toggles between the full takeover card and a
(#1118, PR #1398 from **@linzhiqin2003**) — `reasoning_content` bottom-line summary — the transcript stays visible while you decide.
follows the latest user message language, not the project context's - **`@`-mention truncation no longer splits CJK codepoints** (PR #1495
inferred `lang`. from **@CrepuscularIRIS / autoghclaw**). Files larger than 128 KB
- **Web search filters spam-stuffed SERPs** (#964, PR #1396 from used to truncate mid-codepoint; the truncator now rounds down to the
**@linzhiqin2003**) — Bing / DDG fallback paths drop the last valid UTF-8 boundary.
generated-content / SEO-farm domains that were poisoning quick - **Startup empty-state shows the build version**, active model with a
lookups. `/model` hint, and current working directory (PR #1444 from
- **Auto routing recognises CJK debug / search keywords** (PRs #1401 **@reidliu41**).
and #1402 from **@linzhiqin2003**) — `--model auto` and the - **`/change` slash command** displays the latest CHANGELOG section
reasoning-effort picker correctly route Chinese / Japanese inside the TUI (PR #1416 from **@zhuangbiaowei**).
technical queries instead of falling through to the generic - **Toast overlay no longer renders on top of the composer** (PR #1485
baseline. from **@MeAiRobot**). Approval toasts now clamp to the gap between
- **Deferred tools hydrate schemas before first execution** (#1419, the composer and footer.
PR #1429 from **@SamhandsomeLee**) — `edit_file` and other - **TUI no longer freezes during long-running shell jobs** (PR #1494
deferred tools now load, show their expected fields, and ask the from **@CrepuscularIRIS / autoghclaw**). The job panel's refresh path
model to retry instead of executing guessed argument names. now reads only the tail bytes under the mutex lock instead of cloning
- **DeepSeek aliases replay thinking-mode tool turns** (PR #1428 the entire stdout buffer every 2.5 seconds.
from **@Beltran12138**) — `deepseek-chat` and - **Markdown renderer no longer eats underscores in identifiers** (PR
`deepseek-reasoner` now get the same `reasoning_content` replay #1455 from **@tiger-dog**). `deepseek_tui` and `foo_bar_baz` no longer
treatment as explicit V4 model IDs, avoiding second-turn 400s render half-italic.
after tool calls. - **`/sessions` picker highlights the selected row** more strongly in
- **Skill completions stay under `/skill`** (#1437, PR #1442 from dark terminals (PR #1493 from **@reidliu41**), and no longer shows
**@reidliu41**) — large local skill collections no longer crowd `<turn_meta>` as the session title (PR #1498 from **@wdw8276**).
the root slash-command menu.
- **`edit_file` rejects no-op replacements** (PR #1460 from
**@xiluoduyu**) — identical `search` / `replace` values now fail
validation instead of returning an empty diff.
- **Windows terminal layout gets width-stable glyphs** (#1314,
PR #1465 from **@CrepuscularIRIS**) — header and file-tree icons
no longer rely on SMP emoji that cmd / PowerShell can mismeasure.
- **Ghostty uses low-motion rendering by default** (#1445, PR #1468
from **@CrepuscularIRIS**) — affected terminals avoid animation
flicker without manual config.
- **Docker buildx provenance EPERM failures get a hint** (#1449,
PR #1469 from **@CrepuscularIRIS**) — macOS shell output points at
the provenance flag when that restricted metadata write fails.
- **Windows CMD mouse-wheel fallback scrolls the transcript**
(#1443, PR #1471 from **@CrepuscularIRIS**) — wheel events mapped
to Up / Down no longer cycle composer history when mouse capture
is off.
- **Sync-to-CNB workflow hardened** — explicit `permissions:
contents: read`, narrowed trigger to `main` + `v*` tags (no longer
mirrors feature branches), `actions/checkout` bumped v3 → v4.
- **+438 LOC of new test coverage** for `error_taxonomy`,
`parse_pages_arg`, web-search precedence, and
`sanitize_stream_chunk` control-byte filtering (PRs #1403-#1406
from **@linzhiqin2003**).
Thanks to **@linzhiqin2003** (10 landings this cycle), Thanks to **@CrepuscularIRIS** (4 landings), **@reidliu41** (2 landings),
**@reidliu41** (5 landings), **@CrepuscularIRIS** (4 landings), **@tiger-dog** (2 landings), **@Duducoco**, **@h3c-hexin**,
**@SamhandsomeLee**, **@Beltran12138**, **@Wenjunyun123**, **@NorethSea**, **@MeAiRobot**, **@zhuangbiaowei**, **@wdw8276**,
**@hlx98007**, **@Liu-Vince**, **@xiluoduyu**, and **@MMMarcinho**, **@SamhandsomeLee**, **@sandofree**,
**@shenxiaodaosanhua** for the bug report. **@lucaszhu-hue**, **@muyuliyan**, **@Oliver-ZPLiu**, **@czf0718**,
**@jieshu666**, and **@YaYII**.
--- ---
-141
View File
@@ -1,141 +0,0 @@
# v0.8.6 Backlog — Work Brief for an AI Agent
This is a structured brief for another AI (Claude Opus, DeepSeek V4, or similar) to
understand the full v0.8.6 scope and begin implementation. The repo is
`github.com/Hmbown/DeepSeek-TUI` — Rust workspace, TUI coding agent for DeepSeek V4.
**Branch**: create `feat/v0.8.6` from `main` (current HEAD at v0.8.5 tag).
**All 23 issues are tagged `v0.8.6`** and live in the repo's GitHub Issues.
**Zero open issues outside this list** — the board is clean.
## Project Context
DeepSeek TUI is a terminal-native coding agent. Key architectural points:
- **Dispatcher binary** (`deepseek`) delegates to the TUI binary (`deepseek-tui`)
- **Crate map**: `crates/tui` is the main crate; `crates/cli` handles CLI entry;
`crates/config`, `crates/core`, `crates/tools` etc. are sub-crates
- **Engine pattern**: `core/engine.rs` runs the agent loop, processes tool calls
- **TUI**: ratatui-based, alt-screen, composer at bottom, sidebar at right
- **Config**: `~/.deepseek/config.toml`, profiles, providers, settings
- **Key files to read first**: `docs/ARCHITECTURE.md`, `crates/tui/src/main.rs`,
`crates/tui/src/tui/app.rs`, `crates/tui/src/core/engine.rs`
Read `AGENTS.md` and `CLAUDE.md` in the repo root for build/test commands.
---
## v0.8.6 Issues — Grouped by Theme
### Group A: UX Polish — Transcript & Clipboard (5 issues)
| # | Title | TL;DR |
|---|-------|-------|
| 380 | Inline diff highlighting | Color +/- in apply_patch/edit_file results |
| 379 | Smart clipboard Ctrl+Y | Copy focused cell to system clipboard |
| 375 | Right-click context menu | Per-cell menu: Copy, Open in editor, Re-run, Hide |
| 374 | Clickable file:line | OSC-8 hyperlinks on path:line in tool output |
| 376 | Native-copy escape | Hold Shift to bypass alt-screen for terminal selection |
### Group B: Workspace UX — Navigation & Visibility (4 issues)
| # | Title | TL;DR |
|---|-------|-------|
| 394 | File-tree pane | Ctrl+E toggles left-side workspace navigator |
| 395 | Cycle-boundary visualization | Inline dividers between coherence cycles |
| 396 | Per-turn cache hit chip | Footer shows cache hit % after each turn |
| 388 | Crash-recovery prompt | On restart, offer to restore interrupted turn |
### Group C: Session & History (3 issues)
| # | Title | TL;DR |
|---|-------|-------|
| 383 | /edit — revise and resubmit | Pull last message into composer, re-run turn |
| 384 | /undo — revert last patch | Surgical undo of apply_patch/edit_file/write_file |
| 385 | /diff — session changes | Show git diff since session start |
### Group D: Tools & Intelligence (4 issues)
| # | Title | TL;DR |
|---|-------|-------|
| 389 | Inline LSP diagnostics | Show rust-analyzer errors after each patch |
| 386 | /init — bootstrap AGENTS.md | Auto-detect project type, write starter AGENTS.md |
| 391 | User-defined slash commands | ~/.deepseek/commands/<name>.md templates |
| 392 | /model auto | Heuristic Pro-vs-Flash routing per turn |
### Group E: Infrastructure & Sharing (4 issues)
| # | Title | TL;DR |
|---|-------|-------|
| 390 | /profile — hot-switch config | Switch config profiles in-session without restart |
| 393 | /share — session URL | Export session as static HTML, upload to gist/S3 |
| 387 | In-app self-update | deepseek update fetches + replaces binary |
| 397 | Goal mode | Stated objective, token budget, self-verification tools |
### Group F: Quality & Fixes (3 issues)
| # | Title | TL;DR |
|---|-------|-------|
| 382 | Collapse Steer/Queue/Immediate | One mental model — everything queues, Ctrl+Enter steers |
| 373 | Sidebar Tasks panel ignores shell jobs | Wire shell jobs into Tasks panel |
| 377 | Shrink App state | Group ~200 fields into typed sub-states |
| 378 | Docs: tighten README + ARCHITECTURE | External-reader polish pass |
---
## Suggested Implementation Order
### Wave 1: Foundation (start here)
1. **#377 (refactor App state)** — do this FIRST. Group fields into sub-state structs
before adding more fields. Every subsequent feature touches App.
2. **#382 (collapse Steer/Queue)** — UX clarity fix, low implementation risk.
3. **#373 (Tasks panel shell jobs)** — bugfix, low risk.
### Wave 2: Transcript UX
4. **#380 (inline diff highlighting)** — parser pass on tool output, visible value.
5. **#374 (clickable file:line)** — OSC-8 hyperlinks, high discoverability.
6. **#379 (smart clipboard Ctrl+Y)** — small feature, big ergonomic win.
7. **#375 (right-click context menu)** — depends on mouse event plumbing.
8. **#376 (native-copy escape)** — terminal selection fix.
### Wave 3: Session tools
9. **#383 (/edit)** — requires engine truncation path.
10. **#384 (/undo)** — depends on snapshot infra.
11. **#385 (/diff)** — uses snapshot repo, depends on #380 for rendering.
12. **#388 (crash-recovery prompt)** — uses existing checkpoint infra.
### Wave 4: Intelligence
13. **#386 (/init)** — project detection + AGENTS.md generation.
14. **#389 (LSP diagnostics)** — polls existing LSP client, low-maintenance.
15. **#391 (user-defined commands)** — skills loader reuse.
16. **#392 (/model auto)** — heuristic router, DeepSeek-specific.
### Wave 5: Visibility & sharing
17. **#394 (file-tree pane)** — workspace navigator.
18. **#395 (cycle-boundary visualization)** — coherence cycle dividers.
19. **#396 (cache hit chip)** — footer chip, simple addition.
20. **#393 (/share)** — HTML export, gist/S3 backend.
21. **#387 (self-update)** — binary fetch + verify + replace.
### Wave 6: Docs & goal mode
22. **#378 (docs polish)** — README + ARCHITECTURE refresh.
23. **#397 (Goal mode)** — largest feature, last (benefits from all previous work).
---
## Working Patterns
- **PR-per-issue** (or small clusters). Each merged PR closes one issue.
- **Decomposition first**: read the issue body, identify the files that need to change,
create a `checklist_write`, then implement.
- **Test gates**: `cargo test --workspace --all-features` must pass before each PR.
- **Lint gates**: `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean.
- **Format**: `cargo fmt --all` before commit.
- **GitHub**: push to `feat/v0.8.6`, create PRs to `main`. Use `gh` CLI.
- **No open issues** except the v0.8.6 list — if new issues emerge, create them but don't block.
## Key Resources
- Repo: `https://github.com/Hmbown/DeepSeek-TUI`
- Architecture: `docs/ARCHITECTURE.md`
- Config reference: `docs/CONFIGURATION.md`
- CLI: `gh issue list --label v0.8.6 --json number,title,body` for full issue text
+2
View File
@@ -57,6 +57,8 @@ export async function GET(req: Request) {
const agentEnv: AgentEnv = { const agentEnv: AgentEnv = {
CURATED_KV: env.CURATED_KV, CURATED_KV: env.CURATED_KV,
DEEPSEEK_API_KEY: env.DEEPSEEK_API_KEY, DEEPSEEK_API_KEY: env.DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL: env.DEEPSEEK_BASE_URL ?? process.env.DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL: env.DEEPSEEK_MODEL ?? process.env.DEEPSEEK_MODEL,
GITHUB_TOKEN: env.GITHUB_TOKEN, GITHUB_TOKEN: env.GITHUB_TOKEN,
CRON_SECRET: env.CRON_SECRET, CRON_SECRET: env.CRON_SECRET,
GITHUB_REPO: env.GITHUB_REPO, GITHUB_REPO: env.GITHUB_REPO,
+21 -6
View File
@@ -12,6 +12,7 @@ import {
hasFreshDraft, hasFreshDraft,
logUsage, logUsage,
type AgentDraft, type AgentDraft,
type DeepSeekEnv,
} from "@/lib/community-agent"; } from "@/lib/community-agent";
export interface AgentEnv { export interface AgentEnv {
@@ -22,6 +23,8 @@ export interface AgentEnv {
delete(key: string): Promise<void>; delete(key: string): Promise<void>;
}; };
DEEPSEEK_API_KEY?: string; DEEPSEEK_API_KEY?: string;
DEEPSEEK_BASE_URL?: string;
DEEPSEEK_MODEL?: string;
GITHUB_TOKEN?: string; GITHUB_TOKEN?: string;
CRON_SECRET?: string; CRON_SECRET?: string;
GITHUB_REPO?: string; GITHUB_REPO?: string;
@@ -29,6 +32,13 @@ export interface AgentEnv {
MAINTAINER_GITHUB_PAT?: string; MAINTAINER_GITHUB_PAT?: string;
} }
function dsEnv(env: AgentEnv): DeepSeekEnv {
return {
baseUrl: env.DEEPSEEK_BASE_URL ?? process.env.DEEPSEEK_BASE_URL,
model: env.DEEPSEEK_MODEL ?? process.env.DEEPSEEK_MODEL,
};
}
export async function runCurate(env: AgentEnv): Promise<Record<string, unknown>> { export async function runCurate(env: AgentEnv): Promise<Record<string, unknown>> {
if (!env.DEEPSEEK_API_KEY) { if (!env.DEEPSEEK_API_KEY) {
return { skipped: true, reason: "DEEPSEEK_API_KEY not set" }; return { skipped: true, reason: "DEEPSEEK_API_KEY not set" };
@@ -38,7 +48,7 @@ export async function runCurate(env: AgentEnv): Promise<Record<string, unknown>>
fetchRepoStats(env.GITHUB_TOKEN), fetchRepoStats(env.GITHUB_TOKEN),
fetchFeed(env.GITHUB_TOKEN, 30), fetchFeed(env.GITHUB_TOKEN, 30),
]); ]);
const dispatch = await curate(env.DEEPSEEK_API_KEY, stats, feed); const dispatch = await curate(env.DEEPSEEK_API_KEY, stats, feed, dsEnv(env));
await putDispatch(dispatch); await putDispatch(dispatch);
return { ok: true, headline: dispatch.headline }; return { ok: true, headline: dispatch.headline };
} catch (e) { } catch (e) {
@@ -84,7 +94,8 @@ export async function runTriage(env: AgentEnv): Promise<Record<string, unknown>>
const { content, usage } = await agentChat( const { content, usage } = await agentChat(
[{ role: "system", content: TRIAGE_PROMPT }, { role: "user", content: JSON.stringify(payload) }], [{ role: "system", content: TRIAGE_PROMPT }, { role: "user", content: JSON.stringify(payload) }],
env.DEEPSEEK_API_KEY!, env.DEEPSEEK_API_KEY!,
true true,
dsEnv(env)
); );
const parsed = JSON.parse(content) as { bodyEn: string; bodyZh: string }; const parsed = JSON.parse(content) as { bodyEn: string; bodyZh: string };
const draft: AgentDraft = { const draft: AgentDraft = {
@@ -166,7 +177,8 @@ export async function runPrReview(env: AgentEnv): Promise<Record<string, unknown
const { content, usage } = await agentChat( const { content, usage } = await agentChat(
[{ role: "system", content: PR_REVIEW_PROMPT }, { role: "user", content: JSON.stringify(payload) }], [{ role: "system", content: PR_REVIEW_PROMPT }, { role: "user", content: JSON.stringify(payload) }],
env.DEEPSEEK_API_KEY!, env.DEEPSEEK_API_KEY!,
true true,
dsEnv(env)
); );
const parsed = JSON.parse(content) as { bodyEn: string; bodyZh: string }; const parsed = JSON.parse(content) as { bodyEn: string; bodyZh: string };
const draft: AgentDraft = { const draft: AgentDraft = {
@@ -232,7 +244,8 @@ export async function runStale(env: AgentEnv): Promise<Record<string, unknown>>
const { content, usage } = await agentChat( const { content, usage } = await agentChat(
[{ role: "system", content: STALE_PROMPT }, { role: "user", content: JSON.stringify(payload) }], [{ role: "system", content: STALE_PROMPT }, { role: "user", content: JSON.stringify(payload) }],
env.DEEPSEEK_API_KEY!, env.DEEPSEEK_API_KEY!,
true true,
dsEnv(env)
); );
const parsed = JSON.parse(content) as { bodyEn: string; bodyZh: string }; const parsed = JSON.parse(content) as { bodyEn: string; bodyZh: string };
const draft: AgentDraft = { const draft: AgentDraft = {
@@ -290,7 +303,8 @@ export async function runDupes(env: AgentEnv): Promise<Record<string, unknown>>
const { content, usage } = await agentChat( const { content, usage } = await agentChat(
[{ role: "system", content: DUPES_PROMPT }, { role: "user", content: JSON.stringify({ issues: openIssues }) }], [{ role: "system", content: DUPES_PROMPT }, { role: "user", content: JSON.stringify({ issues: openIssues }) }],
env.DEEPSEEK_API_KEY!, env.DEEPSEEK_API_KEY!,
true true,
dsEnv(env)
); );
const parsed = JSON.parse(content) as { suggestions?: { targetNumber: number; duplicateNumber: number; reason: string; bodyEn: string; bodyZh: string }[] }; const parsed = JSON.parse(content) as { suggestions?: { targetNumber: number; duplicateNumber: number; reason: string; bodyEn: string; bodyZh: string }[] };
@@ -373,7 +387,8 @@ export async function runDigest(env: AgentEnv): Promise<Record<string, unknown>>
const { content, usage } = await agentChat( const { content, usage } = await agentChat(
[{ role: "system", content: DIGEST_PROMPT }, { role: "user", content: JSON.stringify(payload) }], [{ role: "system", content: DIGEST_PROMPT }, { role: "user", content: JSON.stringify(payload) }],
env.DEEPSEEK_API_KEY!, env.DEEPSEEK_API_KEY!,
true true,
dsEnv(env)
); );
const parsed = JSON.parse(content) as { titleEn: string; titleZh: string; summaryEn: string; summaryZh: string; sections: { heading: string; items: string[] }[] }; const parsed = JSON.parse(content) as { titleEn: string; titleZh: string; summaryEn: string; summaryZh: string; sections: { heading: string; items: string[] }[] };
+13 -3
View File
@@ -41,13 +41,19 @@ export interface UsageLog {
outputTokens: number; outputTokens: number;
} }
export interface DeepSeekEnv {
baseUrl?: string;
model?: string;
}
export async function agentChat( export async function agentChat(
messages: ChatMessage[], messages: ChatMessage[],
apiKey: string, apiKey: string,
jsonMode = false jsonMode = false,
dsEnv?: DeepSeekEnv
): Promise<{ content: string; usage: { input: number; output: number } }> { ): Promise<{ content: string; usage: { input: number; output: number } }> {
const base = process.env.DEEPSEEK_BASE_URL ?? FALLBACK_BASE; const base = dsEnv?.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? FALLBACK_BASE;
const model = process.env.DEEPSEEK_MODEL ?? FALLBACK_MODEL; const model = dsEnv?.model ?? process.env.DEEPSEEK_MODEL ?? FALLBACK_MODEL;
const res = await fetch(`${base}/v1/chat/completions`, { const res = await fetch(`${base}/v1/chat/completions`, {
method: "POST", method: "POST",
headers: { headers: {
@@ -185,6 +191,8 @@ interface KVNamespace {
export interface CommunityAgentEnv { export interface CommunityAgentEnv {
CURATED_KV?: KVNamespace; CURATED_KV?: KVNamespace;
DEEPSEEK_API_KEY?: string; DEEPSEEK_API_KEY?: string;
DEEPSEEK_BASE_URL?: string;
DEEPSEEK_MODEL?: string;
GITHUB_TOKEN?: string; GITHUB_TOKEN?: string;
CRON_SECRET?: string; CRON_SECRET?: string;
GITHUB_REPO?: string; GITHUB_REPO?: string;
@@ -200,6 +208,8 @@ export async function getAgentEnv(): Promise<CommunityAgentEnv> {
} catch { } catch {
return { return {
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY, DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL: process.env.DEEPSEEK_MODEL,
GITHUB_TOKEN: process.env.GITHUB_TOKEN, GITHUB_TOKEN: process.env.GITHUB_TOKEN,
CRON_SECRET: process.env.CRON_SECRET, CRON_SECRET: process.env.CRON_SECRET,
GITHUB_REPO: process.env.GITHUB_REPO, GITHUB_REPO: process.env.GITHUB_REPO,
+11 -1
View File
@@ -13,7 +13,7 @@
* Both surface as drafts in CURATED_KV under `draft:linkcheck:<...>` and * Both surface as drafts in CURATED_KV under `draft:linkcheck:<...>` and
* `draft:semantic-drift:<...>`, picked up by the existing /admin listing. * `draft:semantic-drift:<...>`, picked up by the existing /admin listing.
*/ */
import { agentChat, saveDraft, type AgentDraft, VOICE_CONSTRAINTS } from "./community-agent"; import { agentChat, saveDraft, type AgentDraft, type DeepSeekEnv, VOICE_CONSTRAINTS } from "./community-agent";
interface KVNamespace { interface KVNamespace {
get(k: string): Promise<string | null>; get(k: string): Promise<string | null>;
@@ -25,9 +25,18 @@ interface KVNamespace {
interface WatchEnv { interface WatchEnv {
CURATED_KV?: KVNamespace; CURATED_KV?: KVNamespace;
DEEPSEEK_API_KEY?: string; DEEPSEEK_API_KEY?: string;
DEEPSEEK_BASE_URL?: string;
DEEPSEEK_MODEL?: string;
GITHUB_TOKEN?: string; GITHUB_TOKEN?: string;
} }
function dsEnv(env: WatchEnv): DeepSeekEnv {
return {
baseUrl: env.DEEPSEEK_BASE_URL ?? process.env.DEEPSEEK_BASE_URL,
model: env.DEEPSEEK_MODEL ?? process.env.DEEPSEEK_MODEL,
};
}
// --- Link checker --- // --- Link checker ---
// Targets to probe daily. For registries that block bot HEAD/GET (npm, crates.io) // Targets to probe daily. For registries that block bot HEAD/GET (npm, crates.io)
@@ -255,6 +264,7 @@ ${docsText}`;
], ],
env.DEEPSEEK_API_KEY, env.DEEPSEEK_API_KEY,
true, true,
dsEnv(env),
); );
} catch (e) { } catch (e) {
return { ok: false, drafted: 0, reason: `LLM call failed: ${e}` }; return { ok: false, drafted: 0, reason: `LLM call failed: ${e}` };
+17 -5
View File
@@ -12,9 +12,19 @@ interface ChatResponse {
choices: { message: { content: string } }[]; choices: { message: { content: string } }[];
} }
export async function chat(messages: ChatMessage[], apiKey: string, jsonMode = false): Promise<string> { export interface DeepSeekEnv {
const base = process.env.DEEPSEEK_BASE_URL ?? FALLBACK_BASE; baseUrl?: string;
const model = process.env.DEEPSEEK_MODEL ?? FALLBACK_MODEL; model?: string;
}
export async function chat(
messages: ChatMessage[],
apiKey: string,
jsonMode = false,
dsEnv?: DeepSeekEnv
): Promise<string> {
const base = dsEnv?.baseUrl ?? process.env.DEEPSEEK_BASE_URL ?? FALLBACK_BASE;
const model = dsEnv?.model ?? process.env.DEEPSEEK_MODEL ?? FALLBACK_MODEL;
const res = await fetch(`${base}/v1/chat/completions`, { const res = await fetch(`${base}/v1/chat/completions`, {
method: "POST", method: "POST",
headers: { headers: {
@@ -64,7 +74,8 @@ Rules:
export async function curate( export async function curate(
apiKey: string, apiKey: string,
stats: RepoStats, stats: RepoStats,
feed: FeedItem[] feed: FeedItem[],
dsEnv?: DeepSeekEnv
): Promise<CuratedDispatch> { ): Promise<CuratedDispatch> {
const trimmedFeed = feed.slice(0, 25).map((f) => ({ const trimmedFeed = feed.slice(0, 25).map((f) => ({
kind: f.kind, kind: f.kind,
@@ -96,7 +107,8 @@ export async function curate(
{ role: "user", content: JSON.stringify(userPayload, null, 2) }, { role: "user", content: JSON.stringify(userPayload, null, 2) },
], ],
apiKey, apiKey,
true true,
dsEnv
); );
const parsed = JSON.parse(raw) as Omit<CuratedDispatch, "generatedAt">; const parsed = JSON.parse(raw) as Omit<CuratedDispatch, "generatedAt">;
+3 -8
View File
@@ -18,13 +18,8 @@ export interface RepoFacts {
} }
export const FACTS: RepoFacts = { export const FACTS: RepoFacts = {
<<<<<<< Updated upstream "generatedAt": "2026-05-12T19:02:49.213Z",
"generatedAt": "2026-05-12T03:14:50.815Z", "version": "0.8.32",
"version": "0.8.30",
=======
"generatedAt": "2026-05-10T15:06:44.698Z",
"version": "0.8.27",
>>>>>>> Stashed changes
"crates": [ "crates": [
"agent", "agent",
"app-server", "app-server",
@@ -95,7 +90,7 @@ export const FACTS: RepoFacts = {
], ],
"defaultModel": "deepseek-v4-pro", "defaultModel": "deepseek-v4-pro",
"nodeEngines": ">=18", "nodeEngines": ">=18",
"toolCount": 62, "toolCount": 64,
"license": "MIT", "license": "MIT",
"latestRelease": null "latestRelease": null
}; };
+4
View File
@@ -16,6 +16,8 @@ interface KVNamespace {
interface CloudflareEnv { interface CloudflareEnv {
CURATED_KV?: KVNamespace; CURATED_KV?: KVNamespace;
DEEPSEEK_API_KEY?: string; DEEPSEEK_API_KEY?: string;
DEEPSEEK_BASE_URL?: string;
DEEPSEEK_MODEL?: string;
GITHUB_TOKEN?: string; GITHUB_TOKEN?: string;
CRON_SECRET?: string; CRON_SECRET?: string;
GITHUB_REPO?: string; GITHUB_REPO?: string;
@@ -29,6 +31,8 @@ export async function getEnv(): Promise<CloudflareEnv> {
} catch { } catch {
return { return {
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY, DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL,
DEEPSEEK_MODEL: process.env.DEEPSEEK_MODEL,
GITHUB_TOKEN: process.env.GITHUB_TOKEN, GITHUB_TOKEN: process.env.GITHUB_TOKEN,
CRON_SECRET: process.env.CRON_SECRET, CRON_SECRET: process.env.CRON_SECRET,
GITHUB_REPO: process.env.GITHUB_REPO, GITHUB_REPO: process.env.GITHUB_REPO,
+8 -3
View File
@@ -14,10 +14,15 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
const cfgPath = join(__dirname, "..", "wrangler.jsonc"); const cfgPath = join(__dirname, "..", "wrangler.jsonc");
const raw = readFileSync(cfgPath, "utf-8"); const raw = readFileSync(cfgPath, "utf-8");
// Parse JSONC (strip comments, trailing commas) // Parse JSONC (strip comments, trailing commas).
// Use a two-pass approach to avoid mangling URLs: first strip
// line comments that look like comments (preceded by whitespace
// or comma, not part of ://), then strip block comments.
const stripped = raw const stripped = raw
.replace(/\/\/.*$/gm, "") // line comments .replace(/(^|[,\s])\/\/[^\n]*/gm, "$1") // line comments (skips :// in URLs)
.replace(/\/\*[\s\S]*?\*\//g, ""); // block comments .replace(/\/\*[\s\S]*?\*\//g, "") // block comments
.replace(/,\s*}/g, "}") // trailing commas
.replace(/,\s*]/g, "]");
const cfg = JSON.parse(stripped); const cfg = JSON.parse(stripped);
const nss = cfg.kv_namespaces; const nss = cfg.kv_namespaces;