add context.md, strip LLM timestamps, clippy fixes, simplify deploy target
This commit is contained in:
19
Makefile
19
Makefile
@@ -1,4 +1,5 @@
|
||||
REPO := $(shell pwd)
|
||||
SUITE := noc
|
||||
HERA := heradev
|
||||
HERA_DIR := noc
|
||||
IMAGE := noc-suite
|
||||
@@ -28,23 +29,7 @@ docker: build-musl
|
||||
|
||||
# ── systemd deploy ──────────────────────────────────────────────────
|
||||
|
||||
noc.service: noc.service.in
|
||||
sed -e 's|@REPO@|$(REPO)|g' -e 's|@PATH@|$(PATH)|g' $< > $@
|
||||
|
||||
deploy: test build noc.service
|
||||
mkdir -p ~/bin ~/.config/systemd/user
|
||||
systemctl --user stop noc 2>/dev/null || true
|
||||
install target/release/noc ~/bin/noc
|
||||
cp noc.service ~/.config/systemd/user/
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now noc
|
||||
systemctl --user restart noc
|
||||
|
||||
SUITE := noc
|
||||
SUITE_DIR := noc
|
||||
GITEA_VERSION := 1.23
|
||||
|
||||
deploy-suite: build
|
||||
deploy: test build
|
||||
ssh $(SUITE) 'mkdir -p ~/bin /data/noc/tools ~/.config/systemd/user && systemctl --user stop noc 2>/dev/null || true'
|
||||
scp target/release/noc $(SUITE):~/bin/
|
||||
scp config.suite.yaml $(SUITE):/data/noc/config.yaml
|
||||
|
||||
43
context.md
Normal file
43
context.md
Normal file
@@ -0,0 +1,43 @@
|
||||
你运行在 suite VPS (Ubuntu 24.04, 4C8G) 上,域名 famzheng.me。
|
||||
|
||||
### 服务架构
|
||||
- **noc**: systemd user service, binary ~/bin/noc, 数据 /data/noc/
|
||||
- **Gitea**: Docker container (gitea/gitea:1.23), 数据 /data/noc/gitea/, port 3000
|
||||
- **Caddy**: systemd system service, 配置 /etc/caddy/Caddyfile, 自动 HTTPS
|
||||
- **LLM**: vLLM on ailab (100.84.7.49:8000), gemma-4-31B-it-AWQ
|
||||
- **Claude Code**: ~/.local/bin/claude (子代<E5AD90><E4BBA3>执行引擎)
|
||||
- **uv**: ~/.local/bin/uv (Python 包管理)
|
||||
|
||||
### 域名路由 (Caddy)
|
||||
- famzheng.me — 主站(占位)
|
||||
- git.famzheng.me → Gitea (localhost:3000)
|
||||
- 新增子域名:编辑 /etc/caddy/Caddyfile,然后 `sudo systemctl reload caddy`
|
||||
|
||||
### Caddy 管理
|
||||
Caddyfile 路径: /etc/caddy/Caddyfile
|
||||
添加新站点示例:
|
||||
```
|
||||
app.famzheng.me {
|
||||
root * /data/www/app
|
||||
file_server
|
||||
}
|
||||
```
|
||||
或反向代理:
|
||||
```
|
||||
api.famzheng.me {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
```
|
||||
修改后执行 `sudo systemctl reload caddy` 生效。
|
||||
Caddy 自动申请和续期 Let's Encrypt 证书,无需手动管理。
|
||||
|
||||
### Gitea
|
||||
- URL: https://git.famzheng.me
|
||||
- Admin: noc (token 在 /data/noc/gitea-token)
|
||||
- 可通过 call_gitea_api 工具或 spawn_agent 管理
|
||||
|
||||
### 可用工具
|
||||
- run_shell: 直接执行 shell 命令
|
||||
- run_python: uv run 执行 Python(支持 deps 自动安装)
|
||||
- spawn_agent: 复杂任务交给 Claude Code 子代理
|
||||
- 管理 Caddy、部署 web app 等基础设施操作,优先用 spawn_agent
|
||||
@@ -16,6 +16,7 @@ pub struct Config {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct GiteaConfig {
|
||||
pub url: String,
|
||||
/// Direct token or read from token_file at startup
|
||||
|
||||
@@ -6,6 +6,25 @@ use teloxide::types::ParseMode;
|
||||
|
||||
use crate::stream::{CURSOR, TG_MSG_LIMIT};
|
||||
|
||||
/// Strip leading timestamps that LLM copies from our injected message timestamps.
|
||||
/// Matches patterns like `[2026-04-10 21:13:15]` or `[2026-04-10 21:13]` at the start.
|
||||
pub fn strip_leading_timestamp(s: &str) -> &str {
|
||||
let trimmed = s.trim_start();
|
||||
if trimmed.starts_with('[') {
|
||||
if let Some(end) = trimmed.find(']') {
|
||||
let inside = &trimmed[1..end];
|
||||
// check if it looks like a timestamp: starts with 20xx-
|
||||
if inside.len() >= 16 && inside.starts_with("20") && inside.contains('-') {
|
||||
let after = trimmed[end + 1..].trim_start();
|
||||
if !after.is_empty() {
|
||||
return after;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
pub fn truncate_for_display(s: &str) -> String {
|
||||
let budget = TG_MSG_LIMIT - CURSOR.len() - 1;
|
||||
if s.len() <= budget {
|
||||
|
||||
@@ -6,7 +6,7 @@ use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::post;
|
||||
use axum::Json;
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::config::GiteaConfig;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ use crate::stream::{send_message_draft, DRAFT_INTERVAL_MS, EDIT_INTERVAL_MS, TG_
|
||||
pub struct TelegramOutput {
|
||||
pub bot: Bot,
|
||||
pub chat_id: ChatId,
|
||||
#[allow(dead_code)]
|
||||
pub is_private: bool,
|
||||
// internal state
|
||||
msg_id: Option<teloxide::types::MessageId>,
|
||||
@@ -140,6 +141,7 @@ impl Output for TelegramOutput {
|
||||
|
||||
use crate::gitea::GiteaClient;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct GiteaOutput {
|
||||
pub client: GiteaClient,
|
||||
pub owner: String,
|
||||
@@ -173,10 +175,12 @@ impl Output for GiteaOutput {
|
||||
|
||||
// ── Buffer (for Worker, tests) ─────────────────────────────────────
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct BufferOutput {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl BufferOutput {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::display::truncate_at_char_boundary;
|
||||
use crate::display::{strip_leading_timestamp, truncate_at_char_boundary};
|
||||
use crate::output::Output;
|
||||
use crate::state::AppState;
|
||||
use crate::tools::{discover_tools, execute_tool, ToolCall};
|
||||
@@ -209,11 +209,14 @@ pub async fn run_openai_with_tools(
|
||||
continue;
|
||||
}
|
||||
|
||||
if !accumulated.is_empty() {
|
||||
let _ = output.finalize(&accumulated).await;
|
||||
// strip timestamps that LLM copies from our message format
|
||||
let cleaned = strip_leading_timestamp(&accumulated).to_string();
|
||||
|
||||
if !cleaned.is_empty() {
|
||||
let _ = output.finalize(&cleaned).await;
|
||||
}
|
||||
|
||||
return Ok(accumulated);
|
||||
return Ok(cleaned);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +260,19 @@ pub fn build_system_prompt(summary: &str, persona: &str, memory_slots: &[(i32, S
|
||||
text.push_str(inner_state);
|
||||
}
|
||||
|
||||
// inject context file if present (e.g. /data/noc/context.md)
|
||||
let config_path = std::env::var("NOC_CONFIG").unwrap_or_else(|_| "config.yaml".into());
|
||||
let context_path = std::path::Path::new(&config_path)
|
||||
.parent()
|
||||
.unwrap_or(std::path::Path::new("."))
|
||||
.join("context.md");
|
||||
if let Ok(ctx) = std::fs::read_to_string(&context_path) {
|
||||
if !ctx.trim().is_empty() {
|
||||
text.push_str("\n\n## 运行环境\n");
|
||||
text.push_str(ctx.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if !summary.is_empty() {
|
||||
text.push_str("\n\n## 之前的对话总结\n");
|
||||
text.push_str(summary);
|
||||
|
||||
@@ -7,7 +7,7 @@ use anyhow::Result;
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::display::truncate_at_char_boundary;
|
||||
|
||||
Reference in New Issue
Block a user