feat: startup git clone for template repo + pass config through
- ensure_repo_ready() at startup: clone if missing, fetch if exists - TemplateRepoConfig gains local_path field - list_all_templates/select_template/extract_repo_template accept repo config - Remove hardcoded repo_dir(), use config.local_path
This commit is contained in:
parent
815477a73b
commit
d4d9edeb78
16
doc/todo.md
16
doc/todo.md
@ -1,7 +1,13 @@
|
|||||||
需求输入和展示,多行,复杂需求,界面不够优化,输入框不够大。
|
# Tori TODO
|
||||||
|
|
||||||
❯ 在前端,计划和日志,每一个条目,都应有一个小小的comment按钮,按一下,直接快速引用,然后输入docus到反馈输入那里,表示要评论的是这
|
## 前端
|
||||||
个地方。这样llm也知道用户具体在指啥。 同时,允许多处引用,再点一个其他的comment按钮,就引用两处,等等。按钮做的不要太眨眼间,比
|
|
||||||
如用hover显示或者就是小一点不占地方,但要ui意图清晰易用。
|
|
||||||
|
|
||||||
时间观察app
|
- [ ] 需求输入优化 — 多行、复杂需求时输入框不够大,展示不够好
|
||||||
|
- [ ] 时间观察 app(timer/scheduler 可视化)
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
|
||||||
|
- [ ] 回退粒度 — 支持有选择地回退某些步骤,而非 docker-cache 式全部 invalidate
|
||||||
|
- [ ] 长任务生命周期 — `execute` 同步等待不适合 30min+ 的构建/测试任务
|
||||||
|
- [ ] 产出物累积 — scratchpad 之外需要结构化的"工作产出"概念
|
||||||
|
- [ ] 上下文管理 — `current_step_chat_history` 需要 token 预算控制 + 历史截断
|
||||||
|
|||||||
13
src/agent.rs
13
src/agent.rs
@ -73,11 +73,17 @@ pub struct AgentManager {
|
|||||||
next_port: AtomicU16,
|
next_port: AtomicU16,
|
||||||
pool: SqlitePool,
|
pool: SqlitePool,
|
||||||
llm_config: LlmConfig,
|
llm_config: LlmConfig,
|
||||||
|
template_repo: Option<crate::TemplateRepoConfig>,
|
||||||
kb: Option<Arc<crate::kb::KbManager>>,
|
kb: Option<Arc<crate::kb::KbManager>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AgentManager {
|
impl AgentManager {
|
||||||
pub fn new(pool: SqlitePool, llm_config: LlmConfig, kb: Option<Arc<crate::kb::KbManager>>) -> Arc<Self> {
|
pub fn new(
|
||||||
|
pool: SqlitePool,
|
||||||
|
llm_config: LlmConfig,
|
||||||
|
template_repo: Option<crate::TemplateRepoConfig>,
|
||||||
|
kb: Option<Arc<crate::kb::KbManager>>,
|
||||||
|
) -> Arc<Self> {
|
||||||
Arc::new(Self {
|
Arc::new(Self {
|
||||||
agents: RwLock::new(HashMap::new()),
|
agents: RwLock::new(HashMap::new()),
|
||||||
broadcast: RwLock::new(HashMap::new()),
|
broadcast: RwLock::new(HashMap::new()),
|
||||||
@ -85,6 +91,7 @@ impl AgentManager {
|
|||||||
next_port: AtomicU16::new(9100),
|
next_port: AtomicU16::new(9100),
|
||||||
pool,
|
pool,
|
||||||
llm_config,
|
llm_config,
|
||||||
|
template_repo,
|
||||||
kb,
|
kb,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -211,7 +218,7 @@ async fn agent_loop(
|
|||||||
tracing::info!("Using forced template: {:?}", forced_template);
|
tracing::info!("Using forced template: {:?}", forced_template);
|
||||||
forced_template
|
forced_template
|
||||||
} else {
|
} else {
|
||||||
template::select_template(&llm, &requirement).await
|
template::select_template(&llm, &requirement, mgr.template_repo.as_ref()).await
|
||||||
};
|
};
|
||||||
let loaded_template = if let Some(ref tid) = template_id {
|
let loaded_template = if let Some(ref tid) = template_id {
|
||||||
tracing::info!("Template selected for workflow {}: {}", workflow_id, tid);
|
tracing::info!("Template selected for workflow {}: {}", workflow_id, tid);
|
||||||
@ -219,7 +226,7 @@ async fn agent_loop(
|
|||||||
|
|
||||||
if template::is_repo_template(tid) {
|
if template::is_repo_template(tid) {
|
||||||
// Repo template: extract from git then load
|
// Repo template: extract from git then load
|
||||||
match template::extract_repo_template(tid).await {
|
match template::extract_repo_template(tid, mgr.template_repo.as_ref()).await {
|
||||||
Ok(template_dir) => {
|
Ok(template_dir) => {
|
||||||
if let Err(e) = template::apply_template(&template_dir, &workdir).await {
|
if let Err(e) = template::apply_template(&template_dir, &workdir).await {
|
||||||
tracing::error!("Failed to apply repo template {}: {}", tid, e);
|
tracing::error!("Failed to apply repo template {}: {}", tid, e);
|
||||||
|
|||||||
@ -197,6 +197,8 @@ async fn list_llm_calls(
|
|||||||
.map_err(db_err)
|
.map_err(db_err)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_templates() -> Json<Vec<template::TemplateListItem>> {
|
async fn list_templates(
|
||||||
Json(template::list_all_templates().await)
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> Json<Vec<template::TemplateListItem>> {
|
||||||
|
Json(template::list_all_templates(state.config.template_repo.as_ref()).await)
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/main.rs
16
src/main.rs
@ -38,6 +38,16 @@ pub struct TemplateRepoConfig {
|
|||||||
pub gitea_url: String,
|
pub gitea_url: String,
|
||||||
pub owner: String,
|
pub owner: String,
|
||||||
pub repo: String,
|
pub repo: String,
|
||||||
|
#[serde(default = "default_repo_path")]
|
||||||
|
pub local_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_repo_path() -> String {
|
||||||
|
if std::path::Path::new("/app/oseng-templates").is_dir() {
|
||||||
|
"/app/oseng-templates".to_string()
|
||||||
|
} else {
|
||||||
|
"oseng-templates".to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Deserialize)]
|
||||||
@ -84,9 +94,15 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ensure template repo is cloned before serving
|
||||||
|
if let Some(ref repo_cfg) = config.template_repo {
|
||||||
|
template::ensure_repo_ready(repo_cfg).await;
|
||||||
|
}
|
||||||
|
|
||||||
let agent_mgr = agent::AgentManager::new(
|
let agent_mgr = agent::AgentManager::new(
|
||||||
database.pool.clone(),
|
database.pool.clone(),
|
||||||
config.llm.clone(),
|
config.llm.clone(),
|
||||||
|
config.template_repo.clone(),
|
||||||
kb_arc.clone(),
|
kb_arc.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::TemplateRepoConfig;
|
||||||
use crate::llm::{ChatMessage, LlmClient};
|
use crate::llm::{ChatMessage, LlmClient};
|
||||||
use crate::tools::ExternalToolManager;
|
use crate::tools::ExternalToolManager;
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ fn builtin_dir() -> &'static str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn repo_dir() -> &'static str {
|
fn default_repo_dir() -> &'static str {
|
||||||
if Path::new("/app/oseng-templates").is_dir() {
|
if Path::new("/app/oseng-templates").is_dir() {
|
||||||
"/app/oseng-templates"
|
"/app/oseng-templates"
|
||||||
} else {
|
} else {
|
||||||
@ -133,8 +134,45 @@ async fn scan_examples_git(repo: &Path, ref_name: &str, template_path: &str) ->
|
|||||||
examples
|
examples
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure the template repo is a git clone; clone or fetch as needed.
|
||||||
|
/// Called at startup (before serve) so readiness probe can gate on it.
|
||||||
|
pub async fn ensure_repo_ready(cfg: &TemplateRepoConfig) {
|
||||||
|
let repo = Path::new(&cfg.local_path);
|
||||||
|
if repo.join(".git").is_dir() {
|
||||||
|
tracing::info!("Template repo already cloned at {}, fetching...", repo.display());
|
||||||
|
let _ = tokio::process::Command::new("git")
|
||||||
|
.args(["fetch", "--all", "--prune", "-q"])
|
||||||
|
.current_dir(repo)
|
||||||
|
.env("GIT_SSL_NO_VERIFY", "true")
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a git repo — remove stale dir (e.g. COPY'd without .git) and clone
|
||||||
|
let url = format!("{}/{}/{}.git", cfg.gitea_url, cfg.owner, cfg.repo);
|
||||||
|
tracing::info!("Cloning template repo {} → {}", url, repo.display());
|
||||||
|
let _ = tokio::fs::remove_dir_all(repo).await;
|
||||||
|
let output = tokio::process::Command::new("git")
|
||||||
|
.args(["clone", &url, &repo.to_string_lossy()])
|
||||||
|
.env("GIT_SSL_NO_VERIFY", "true")
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
match output {
|
||||||
|
Ok(o) if o.status.success() => {
|
||||||
|
tracing::info!("Template repo cloned successfully");
|
||||||
|
}
|
||||||
|
Ok(o) => {
|
||||||
|
tracing::error!("git clone failed: {}", String::from_utf8_lossy(&o.stderr));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("git clone error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// List all templates from both built-in and repo (all branches).
|
/// List all templates from both built-in and repo (all branches).
|
||||||
pub async fn list_all_templates() -> Vec<TemplateListItem> {
|
pub async fn list_all_templates(repo_cfg: Option<&TemplateRepoConfig>) -> Vec<TemplateListItem> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
|
||||||
// 1. Built-in templates (flat: each top-level dir with template.json)
|
// 1. Built-in templates (flat: each top-level dir with template.json)
|
||||||
@ -164,8 +202,11 @@ pub async fn list_all_templates() -> Vec<TemplateListItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Repo templates (all branches, find INSTRUCTIONS.md via git ls-tree)
|
// 2. Repo templates (all branches, cloned at startup)
|
||||||
let repo = Path::new(repo_dir());
|
let repo_path = repo_cfg
|
||||||
|
.map(|c| c.local_path.as_str())
|
||||||
|
.unwrap_or_else(|| default_repo_dir());
|
||||||
|
let repo = Path::new(repo_path);
|
||||||
if repo.join(".git").is_dir() {
|
if repo.join(".git").is_dir() {
|
||||||
items.extend(scan_repo_all_branches(repo).await);
|
items.extend(scan_repo_all_branches(repo).await);
|
||||||
}
|
}
|
||||||
@ -284,8 +325,11 @@ async fn read_git_file_json(
|
|||||||
|
|
||||||
/// Extract a template from a git branch to a local directory.
|
/// Extract a template from a git branch to a local directory.
|
||||||
/// Uses `git archive` to extract the template subtree.
|
/// Uses `git archive` to extract the template subtree.
|
||||||
pub async fn extract_repo_template(template_id: &str) -> anyhow::Result<PathBuf> {
|
pub async fn extract_repo_template(template_id: &str, repo_cfg: Option<&TemplateRepoConfig>) -> anyhow::Result<PathBuf> {
|
||||||
let repo = Path::new(repo_dir());
|
let repo_path = repo_cfg
|
||||||
|
.map(|c| c.local_path.as_str())
|
||||||
|
.unwrap_or_else(|| default_repo_dir());
|
||||||
|
let repo = Path::new(repo_path);
|
||||||
let dest = PathBuf::from("/tmp/tori-repo-templates").join(template_id);
|
let dest = PathBuf::from("/tmp/tori-repo-templates").join(template_id);
|
||||||
let _ = tokio::fs::remove_dir_all(&dest).await;
|
let _ = tokio::fs::remove_dir_all(&dest).await;
|
||||||
tokio::fs::create_dir_all(&dest).await?;
|
tokio::fs::create_dir_all(&dest).await?;
|
||||||
@ -402,8 +446,8 @@ pub fn is_repo_template(template_id: &str) -> bool {
|
|||||||
|
|
||||||
// --- LLM template selection ---
|
// --- LLM template selection ---
|
||||||
|
|
||||||
pub async fn select_template(llm: &LlmClient, requirement: &str) -> Option<String> {
|
pub async fn select_template(llm: &LlmClient, requirement: &str, repo_cfg: Option<&TemplateRepoConfig>) -> Option<String> {
|
||||||
let all = list_all_templates().await;
|
let all = list_all_templates(repo_cfg).await;
|
||||||
if all.is_empty() {
|
if all.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user