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)
|
REPO := $(shell pwd)
|
||||||
|
SUITE := noc
|
||||||
HERA := heradev
|
HERA := heradev
|
||||||
HERA_DIR := noc
|
HERA_DIR := noc
|
||||||
IMAGE := noc-suite
|
IMAGE := noc-suite
|
||||||
@@ -28,23 +29,7 @@ docker: build-musl
|
|||||||
|
|
||||||
# ── systemd deploy ──────────────────────────────────────────────────
|
# ── systemd deploy ──────────────────────────────────────────────────
|
||||||
|
|
||||||
noc.service: noc.service.in
|
deploy: test build
|
||||||
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
|
|
||||||
ssh $(SUITE) 'mkdir -p ~/bin /data/noc/tools ~/.config/systemd/user && systemctl --user stop noc 2>/dev/null || true'
|
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 target/release/noc $(SUITE):~/bin/
|
||||||
scp config.suite.yaml $(SUITE):/data/noc/config.yaml
|
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)]
|
#[derive(Deserialize, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct GiteaConfig {
|
pub struct GiteaConfig {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
/// Direct token or read from token_file at startup
|
/// Direct token or read from token_file at startup
|
||||||
|
|||||||
@@ -6,6 +6,25 @@ use teloxide::types::ParseMode;
|
|||||||
|
|
||||||
use crate::stream::{CURSOR, TG_MSG_LIMIT};
|
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 {
|
pub fn truncate_for_display(s: &str) -> String {
|
||||||
let budget = TG_MSG_LIMIT - CURSOR.len() - 1;
|
let budget = TG_MSG_LIMIT - CURSOR.len() - 1;
|
||||||
if s.len() <= budget {
|
if s.len() <= budget {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::http::StatusCode;
|
|||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::config::GiteaConfig;
|
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 struct TelegramOutput {
|
||||||
pub bot: Bot,
|
pub bot: Bot,
|
||||||
pub chat_id: ChatId,
|
pub chat_id: ChatId,
|
||||||
|
#[allow(dead_code)]
|
||||||
pub is_private: bool,
|
pub is_private: bool,
|
||||||
// internal state
|
// internal state
|
||||||
msg_id: Option<teloxide::types::MessageId>,
|
msg_id: Option<teloxide::types::MessageId>,
|
||||||
@@ -140,6 +141,7 @@ impl Output for TelegramOutput {
|
|||||||
|
|
||||||
use crate::gitea::GiteaClient;
|
use crate::gitea::GiteaClient;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct GiteaOutput {
|
pub struct GiteaOutput {
|
||||||
pub client: GiteaClient,
|
pub client: GiteaClient,
|
||||||
pub owner: String,
|
pub owner: String,
|
||||||
@@ -173,10 +175,12 @@ impl Output for GiteaOutput {
|
|||||||
|
|
||||||
// ── Buffer (for Worker, tests) ─────────────────────────────────────
|
// ── Buffer (for Worker, tests) ─────────────────────────────────────
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct BufferOutput {
|
pub struct BufferOutput {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
impl BufferOutput {
|
impl BufferOutput {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
|||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::config::Config;
|
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::output::Output;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use crate::tools::{discover_tools, execute_tool, ToolCall};
|
use crate::tools::{discover_tools, execute_tool, ToolCall};
|
||||||
@@ -209,11 +209,14 @@ pub async fn run_openai_with_tools(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !accumulated.is_empty() {
|
// strip timestamps that LLM copies from our message format
|
||||||
let _ = output.finalize(&accumulated).await;
|
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);
|
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() {
|
if !summary.is_empty() {
|
||||||
text.push_str("\n\n## 之前的对话总结\n");
|
text.push_str("\n\n## 之前的对话总结\n");
|
||||||
text.push_str(summary);
|
text.push_str(summary);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use anyhow::Result;
|
|||||||
use tokio::io::AsyncBufReadExt;
|
use tokio::io::AsyncBufReadExt;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::display::truncate_at_char_boundary;
|
use crate::display::truncate_at_char_boundary;
|
||||||
|
|||||||
Reference in New Issue
Block a user