feat: initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .settings import Settings, config
|
||||
|
||||
__all__ = ["Settings", "config"]
|
||||
@@ -0,0 +1,234 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from schemas.agent.system_agent import SystemAgentLLMConfig
|
||||
from core.db.session import AsyncSessionLocal
|
||||
from core.logging import get_logger
|
||||
from models.llm import Llm
|
||||
from models.llm_factory import LlmFactory
|
||||
from models.system_agents import SystemAgents
|
||||
|
||||
logger = get_logger("core.config.initial.init_data")
|
||||
|
||||
|
||||
class LlmFactorySeed(BaseModel):
|
||||
name: str
|
||||
request_url: str
|
||||
avatar: str | None = None
|
||||
|
||||
|
||||
class LlmSeed(BaseModel):
|
||||
model_code: str
|
||||
factory_name: str
|
||||
pricing_tiers: list[dict[str, float | int]]
|
||||
|
||||
|
||||
class LlmCatalogSeed(BaseModel):
|
||||
factories: list[LlmFactorySeed]
|
||||
llms: list[LlmSeed]
|
||||
|
||||
|
||||
class SystemAgentsSeed(BaseModel):
|
||||
agent_type: str
|
||||
llm_model_code: str
|
||||
status: str
|
||||
config: SystemAgentLLMConfig | None = None
|
||||
|
||||
|
||||
class SystemAgentsYaml(BaseModel):
|
||||
agents: list[SystemAgentsSeed]
|
||||
|
||||
|
||||
def _default_catalog_path() -> Path:
|
||||
return (
|
||||
Path(__file__).resolve().parents[1] / "static" / "database" / "llm_catalog.yaml"
|
||||
)
|
||||
|
||||
|
||||
def load_llm_catalog(catalog_path: Path | None = None) -> dict[str, Any]:
|
||||
path = catalog_path or _default_catalog_path()
|
||||
with path.open("r", encoding="utf-8") as file:
|
||||
loaded = yaml.safe_load(file) or {}
|
||||
if not isinstance(loaded, dict):
|
||||
raise ValueError(f"Invalid LLM catalog format: {path}")
|
||||
raw_factories = loaded.get("factories", [])
|
||||
raw_llms = loaded.get("llms", [])
|
||||
if not isinstance(raw_factories, list) or not isinstance(raw_llms, list):
|
||||
raise ValueError(f"Invalid LLM catalog sections: {path}")
|
||||
try:
|
||||
parsed = LlmCatalogSeed.model_validate(
|
||||
{
|
||||
"factories": list(raw_factories),
|
||||
"llms": list(raw_llms),
|
||||
}
|
||||
)
|
||||
except ValidationError as exc:
|
||||
raise ValueError(f"Invalid LLM catalog data: {path}") from exc
|
||||
|
||||
return parsed.model_dump()
|
||||
|
||||
|
||||
def _default_system_agents_path() -> Path:
|
||||
return (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "static"
|
||||
/ "database"
|
||||
/ "system_agents.yaml"
|
||||
)
|
||||
|
||||
|
||||
def load_system_agents(catalog_path: Path | None = None) -> dict[str, Any]:
|
||||
path = catalog_path or _default_system_agents_path()
|
||||
with path.open("r", encoding="utf-8") as file:
|
||||
loaded = yaml.safe_load(file) or {}
|
||||
if not isinstance(loaded, dict):
|
||||
raise ValueError(f"Invalid system agents format: {path}")
|
||||
raw_agents = loaded.get("agents", [])
|
||||
if not isinstance(raw_agents, list):
|
||||
raise ValueError(f"Invalid system agents agents section: {path}")
|
||||
try:
|
||||
parsed = SystemAgentsYaml.model_validate({"agents": list(raw_agents)})
|
||||
except ValidationError as exc:
|
||||
raise ValueError(f"Invalid system agents data: {path}") from exc
|
||||
|
||||
return parsed.model_dump()
|
||||
|
||||
|
||||
async def _upsert_factory(
|
||||
session: AsyncSession,
|
||||
*,
|
||||
name: str,
|
||||
request_url: str,
|
||||
avatar: str | None,
|
||||
) -> uuid.UUID:
|
||||
result = await session.execute(select(LlmFactory).where(LlmFactory.name == name))
|
||||
factory = result.scalar_one_or_none()
|
||||
|
||||
if factory is None:
|
||||
factory = LlmFactory(name=name, request_url=request_url, avatar=avatar)
|
||||
session.add(factory)
|
||||
await session.flush()
|
||||
else:
|
||||
factory.request_url = request_url
|
||||
factory.avatar = avatar
|
||||
|
||||
return factory.id
|
||||
|
||||
|
||||
async def _upsert_llm(
|
||||
session: AsyncSession,
|
||||
*,
|
||||
model_code: str,
|
||||
factory_id: uuid.UUID,
|
||||
) -> None:
|
||||
result = await session.execute(select(Llm).where(Llm.model_code == model_code))
|
||||
llm = result.scalar_one_or_none()
|
||||
if llm is None:
|
||||
session.add(Llm(model_code=model_code, factory_id=factory_id))
|
||||
return
|
||||
llm.factory_id = factory_id
|
||||
|
||||
|
||||
async def _upsert_system_agents(
|
||||
session: AsyncSession,
|
||||
*,
|
||||
agent_type: str,
|
||||
llm_id: uuid.UUID,
|
||||
status: str,
|
||||
config: dict[str, Any],
|
||||
) -> None:
|
||||
result = await session.execute(
|
||||
select(SystemAgents).where(SystemAgents.agent_type == agent_type)
|
||||
)
|
||||
catalog_entry = result.scalar_one_or_none()
|
||||
|
||||
if catalog_entry is None:
|
||||
session.add(
|
||||
SystemAgents(
|
||||
agent_type=agent_type,
|
||||
llm_id=llm_id,
|
||||
status=status,
|
||||
config=config,
|
||||
)
|
||||
)
|
||||
else:
|
||||
catalog_entry.llm_id = llm_id
|
||||
catalog_entry.status = status
|
||||
catalog_entry.config = config
|
||||
|
||||
|
||||
async def initialize_system_agents() -> None:
|
||||
"""Initialize system agents from YAML."""
|
||||
catalog = load_system_agents()
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
for agent in catalog["agents"]:
|
||||
result = await session.execute(
|
||||
select(Llm).where(Llm.model_code == agent["llm_model_code"])
|
||||
)
|
||||
llm = result.scalar_one_or_none()
|
||||
if llm is None:
|
||||
raise RuntimeError(
|
||||
f"LLM model '{agent['llm_model_code']}' not found for agent type '{agent['agent_type']}'"
|
||||
)
|
||||
|
||||
await _upsert_system_agents(
|
||||
session,
|
||||
agent_type=agent["agent_type"],
|
||||
llm_id=llm.id,
|
||||
status=agent["status"],
|
||||
config=SystemAgentLLMConfig.model_validate(
|
||||
agent.get("config") or {}
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
logger.info("Initialized system agents")
|
||||
|
||||
|
||||
async def initialize_llm_catalog() -> None:
|
||||
"""Initialize LLM catalog from YAML."""
|
||||
catalog = load_llm_catalog()
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
async with session.begin():
|
||||
factory_id_by_name: dict[str, uuid.UUID] = {}
|
||||
for factory in catalog["factories"]:
|
||||
factory_id = await _upsert_factory(
|
||||
session,
|
||||
name=factory["name"],
|
||||
request_url=factory["request_url"],
|
||||
avatar=factory.get("avatar"),
|
||||
)
|
||||
factory_id_by_name[factory["name"]] = factory_id
|
||||
|
||||
for llm in catalog["llms"]:
|
||||
factory_name = llm["factory_name"]
|
||||
resolved_factory_id = factory_id_by_name.get(factory_name)
|
||||
if resolved_factory_id is None:
|
||||
raise RuntimeError(
|
||||
f"Factory '{factory_name}' not found for model '{llm['model_code']}'"
|
||||
)
|
||||
await _upsert_llm(
|
||||
session,
|
||||
model_code=llm["model_code"],
|
||||
factory_id=resolved_factory_id,
|
||||
)
|
||||
|
||||
logger.info("Initialized LLM factory/model seed data")
|
||||
|
||||
|
||||
async def initialize_data() -> bool:
|
||||
"""Initialize bootstrap data."""
|
||||
await initialize_llm_catalog()
|
||||
await initialize_system_agents()
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,263 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, Literal
|
||||
from urllib.parse import quote
|
||||
|
||||
from pydantic import (
|
||||
AnyHttpUrl,
|
||||
BaseModel,
|
||||
Field,
|
||||
computed_field,
|
||||
field_validator,
|
||||
model_validator,
|
||||
)
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
def _resolve_project_root() -> Path:
|
||||
current = Path(__file__).resolve()
|
||||
for parent in current.parents:
|
||||
if (
|
||||
(parent / "pyproject.toml").is_file()
|
||||
and (parent / "backend").is_dir()
|
||||
and (parent / "infra").is_dir()
|
||||
):
|
||||
return parent
|
||||
|
||||
for parent in current.parents:
|
||||
if parent.name == "backend":
|
||||
return parent.parent
|
||||
|
||||
return Path.cwd().resolve()
|
||||
|
||||
|
||||
class RuntimeSettings(BaseModel):
|
||||
environment: Literal["dev", "test", "prod"] = "dev"
|
||||
service_name: str = "app"
|
||||
debug: bool = True
|
||||
log_level: str = "INFO"
|
||||
log_json: bool = True
|
||||
log_rotation: Literal["time", "size", "none"] = "time"
|
||||
log_rotation_when: str = "midnight"
|
||||
log_rotation_interval: int = 1
|
||||
log_rotation_backup_count: int = 14
|
||||
log_rotation_max_bytes: int = 10_000_000
|
||||
log_dir: str = "logs"
|
||||
log_error_dir: str = "logs/errors"
|
||||
log_file_name: str = ""
|
||||
log_error_file_name: str = ""
|
||||
log_sensitive_fields: list[str] = Field(
|
||||
default_factory=lambda: [
|
||||
"password",
|
||||
"secret",
|
||||
"token",
|
||||
"api_key",
|
||||
"authorization",
|
||||
"cookie",
|
||||
"client_ip",
|
||||
"user_id",
|
||||
]
|
||||
)
|
||||
sql_log_queries: bool = False
|
||||
trusted_proxy_ips: list[str] = Field(default_factory=list)
|
||||
|
||||
@field_validator("log_dir", mode="before")
|
||||
@classmethod
|
||||
def lock_log_dir(cls, _: object) -> str:
|
||||
return "logs"
|
||||
|
||||
@field_validator("log_error_dir", mode="before")
|
||||
@classmethod
|
||||
def lock_log_error_dir(cls, _: object) -> str:
|
||||
return "logs/errors"
|
||||
|
||||
@model_validator(mode="after")
|
||||
def ensure_service_scoped_log_file_names(self) -> "RuntimeSettings":
|
||||
service = "".join(
|
||||
char if char.isalnum() or char in {"-", "_"} else "-"
|
||||
for char in self.service_name
|
||||
).strip("-_")
|
||||
service_name = service or "app"
|
||||
|
||||
if not self.log_file_name.strip():
|
||||
self.log_file_name = f"{service_name}.log"
|
||||
if not self.log_error_file_name.strip():
|
||||
self.log_error_file_name = f"{service_name}.error.log"
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class CorsSettings(BaseModel):
|
||||
allow_origins: list[str] = Field(
|
||||
default_factory=lambda: [
|
||||
"http://localhost",
|
||||
"http://localhost:3000",
|
||||
]
|
||||
)
|
||||
allow_credentials: bool = True
|
||||
allow_methods: list[str] = Field(default_factory=lambda: ["*"])
|
||||
allow_headers: list[str] = Field(default_factory=lambda: ["*"])
|
||||
|
||||
|
||||
class RedisSettings(BaseModel):
|
||||
host: str = "redis"
|
||||
port: int = 6379
|
||||
password: str | None = None
|
||||
db: int = 0
|
||||
socket_connect_timeout: float = 1.0
|
||||
socket_timeout: float = 1.0
|
||||
max_connections: int = 10
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def url(self) -> str:
|
||||
if self.password:
|
||||
password = quote(self.password, safe="")
|
||||
return f"redis://:{password}@{self.host}:{self.port}/{self.db}"
|
||||
return f"redis://{self.host}:{self.port}/{self.db}"
|
||||
|
||||
|
||||
class DatabaseSettings(BaseModel):
|
||||
host: str = "localhost"
|
||||
port: int = 3306
|
||||
name: str = "eryao"
|
||||
user: str = "root"
|
||||
password: str = "CHANGE_ME"
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def url(self) -> str:
|
||||
password = quote(self.password, safe="")
|
||||
return (
|
||||
f"mysql+aiomysql://{self.user}:{password}"
|
||||
f"@{self.host}:{self.port}/{self.name}"
|
||||
)
|
||||
|
||||
|
||||
class AppVersionSettings(BaseModel):
|
||||
manifest_path: str = Field(
|
||||
default="deploy/static/releases/manifest.json",
|
||||
description="发布清单文件路径,相对于项目根目录",
|
||||
)
|
||||
release_path_prefix: str = Field(
|
||||
default="releases",
|
||||
description="下载 URL 中文件目录前缀",
|
||||
)
|
||||
download_base_url: AnyHttpUrl | None = Field(
|
||||
default=None,
|
||||
description="下载链接基础域名,如 https://your-domain.com",
|
||||
)
|
||||
|
||||
@field_validator("download_base_url", mode="before")
|
||||
@classmethod
|
||||
def empty_download_base_url_to_none(cls, value: object) -> object:
|
||||
if value == "":
|
||||
return None
|
||||
return value
|
||||
|
||||
@field_validator("manifest_path")
|
||||
@classmethod
|
||||
def validate_manifest_path(cls, value: str) -> str:
|
||||
normalized = Path(value)
|
||||
if normalized.is_absolute() or ".." in normalized.parts:
|
||||
raise ValueError("manifest_path must be a safe relative path")
|
||||
return value
|
||||
|
||||
|
||||
class AliyunSmsSettings(BaseModel):
|
||||
access_key_id: str = "CHANGE_ME"
|
||||
access_key_secret: str = "CHANGE_ME"
|
||||
sign_name: str = "CHANGE_ME"
|
||||
template_code: str = "CHANGE_ME"
|
||||
region_id: str = "cn-hangzhou"
|
||||
endpoint: str = "dysmsapi.aliyuncs.com"
|
||||
test_mode: bool = False
|
||||
|
||||
|
||||
class AliyunContentSecuritySettings(BaseModel):
|
||||
access_key_id: str = "CHANGE_ME"
|
||||
access_key_secret: str = "CHANGE_ME"
|
||||
endpoint: str = "green-cip.cn-shenzhen.aliyuncs.com"
|
||||
|
||||
|
||||
class AlipaySettings(BaseModel):
|
||||
app_id: str = "CHANGE_ME"
|
||||
merchant_id: str = "CHANGE_ME"
|
||||
public_key: str = "CHANGE_ME"
|
||||
private_key: str = "CHANGE_ME"
|
||||
sign_type: str = "RSA2"
|
||||
notify_url: str = ""
|
||||
timeout_express: str = "30m"
|
||||
sandbox: bool = False
|
||||
|
||||
|
||||
class DeepSeekSettings(BaseModel):
|
||||
api_key: str = "CHANGE_ME"
|
||||
|
||||
|
||||
class AuthSettings(BaseModel):
|
||||
token_expiration_days: int = 7
|
||||
token_refresh_threshold_hours: int = 2
|
||||
|
||||
|
||||
class VerificationSettings(BaseModel):
|
||||
code_length: int = 6
|
||||
expiration_minutes: int = 5
|
||||
test_mode: bool = False
|
||||
|
||||
|
||||
class SensitiveWordSettings(BaseModel):
|
||||
use_aliyun: bool = True
|
||||
fallback_to_local: bool = True
|
||||
|
||||
|
||||
class TestSettings(BaseModel):
|
||||
phone: str = ""
|
||||
password: str = ""
|
||||
|
||||
|
||||
def _resolve_env_file() -> str:
|
||||
current = Path(__file__).resolve()
|
||||
for parent in [current, *current.parents]:
|
||||
candidate = parent / ".env"
|
||||
if candidate.is_file():
|
||||
return str(candidate)
|
||||
return ".env"
|
||||
|
||||
|
||||
PROJECT_ROOT = _resolve_project_root()
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
runtime: RuntimeSettings = RuntimeSettings()
|
||||
cors: CorsSettings = CorsSettings()
|
||||
redis: RedisSettings = RedisSettings()
|
||||
database: DatabaseSettings = DatabaseSettings()
|
||||
app_version: AppVersionSettings = AppVersionSettings()
|
||||
aliyun_sms: AliyunSmsSettings = AliyunSmsSettings()
|
||||
aliyun_content_security: AliyunContentSecuritySettings = (
|
||||
AliyunContentSecuritySettings()
|
||||
)
|
||||
alipay: AlipaySettings = AlipaySettings()
|
||||
deepseek: DeepSeekSettings = DeepSeekSettings()
|
||||
auth: AuthSettings = AuthSettings()
|
||||
verification: VerificationSettings = VerificationSettings()
|
||||
sensitive_word: SensitiveWordSettings = SensitiveWordSettings()
|
||||
test: TestSettings = Field(default_factory=TestSettings)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def database_url(self) -> str:
|
||||
return self.database.url
|
||||
|
||||
model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
|
||||
env_file=_resolve_env_file(),
|
||||
env_prefix="ERYAO_",
|
||||
env_nested_delimiter="__",
|
||||
case_sensitive=False,
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
|
||||
config = Settings() # type: ignore[reportCallIssue]
|
||||
@@ -0,0 +1,34 @@
|
||||
input_template: |
|
||||
你正在执行一次"自动化记忆回顾与整理"任务。
|
||||
|
||||
任务目标:
|
||||
1) 回顾最近两天的聊天与上下文,识别用户长期偏好、习惯和关键事实的变化。
|
||||
2) 对已经失效、被否定或明显过期的信息执行遗忘。
|
||||
3) 对新增且有证据支持的信息执行写入。
|
||||
4) 严禁编造;没有证据就不要写入。
|
||||
5) 只更新最小必要字段,避免过度覆盖。
|
||||
|
||||
输出要求:
|
||||
- 必须使用以下固定格式输出:
|
||||
<----------【周期任务输出】---------->
|
||||
【记忆回顾】<一句人性化总结,说明今天主要发生了什么>
|
||||
【新增记忆】<按"X条:要点1;要点2"描述;没有则写"0条">
|
||||
【遗忘记忆】<按"X条:要点1;要点2"描述;没有则写"0条">
|
||||
【未来展望】<基于本次记忆变化,给出1-2条温和、可执行的后续建议;若暂无建议则说明"可继续观察">
|
||||
|
||||
表达风格:
|
||||
- 语言自然、温和、可读,像助理在做每日回顾。
|
||||
- 结论先行,避免空话,不要输出与任务无关的闲聊内容。
|
||||
enabled_tools:
|
||||
- memory.write
|
||||
- memory.forget
|
||||
context:
|
||||
source: latest_chat
|
||||
window_mode: day
|
||||
window_count: 2
|
||||
schedule:
|
||||
type: daily
|
||||
run_at:
|
||||
hour: 8
|
||||
minute: 0
|
||||
weekdays: null
|
||||
@@ -0,0 +1,70 @@
|
||||
factories:
|
||||
- name: dashscope
|
||||
request_url: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
avatar: https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/light/qwen-color.png
|
||||
|
||||
- name: minimax
|
||||
request_url: https://api.minimaxi.com/v1
|
||||
avatar: https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/light/minimax-color.png
|
||||
|
||||
- name: moonshot
|
||||
request_url: https://api.moonshot.cn/v1
|
||||
avatar: https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/light/moonshot.png
|
||||
|
||||
- name: deepseek
|
||||
request_url: https://api.deepseek.com/v1
|
||||
avatar: https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/light/deepseek-color.png
|
||||
|
||||
- name: volcengine
|
||||
request_url: https://ark.cn-beijing.volces.com/api/v3
|
||||
avatar: https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/light/doubao-color.png
|
||||
|
||||
- name: zai
|
||||
request_url: https://api.z.ai/api/paas/v4
|
||||
avatar: https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/light/zai.png
|
||||
|
||||
llms:
|
||||
# qwen3.5-flash (3 tiers: 128K, 256K, 1M)
|
||||
- model_code: qwen3.5-flash
|
||||
factory_name: dashscope
|
||||
pricing_tiers:
|
||||
- max_prompt_tokens: 128000
|
||||
input_cost_per_token: 0.0000002
|
||||
output_cost_per_token: 0.000002
|
||||
cache_hit_cost_per_token: 0.00000002
|
||||
- max_prompt_tokens: 256000
|
||||
input_cost_per_token: 0.0000008
|
||||
output_cost_per_token: 0.000008
|
||||
cache_hit_cost_per_token: 0.00000008
|
||||
- max_prompt_tokens: 1000000
|
||||
input_cost_per_token: 0.0000012
|
||||
output_cost_per_token: 0.000012
|
||||
cache_hit_cost_per_token: 0.00000012
|
||||
|
||||
- model_code: qwen3.5-35b-a3b
|
||||
factory_name: dashscope
|
||||
pricing_tiers:
|
||||
- max_prompt_tokens: 128000
|
||||
input_cost_per_token: 0.0000004
|
||||
output_cost_per_token: 0.0000032
|
||||
- max_prompt_tokens: 256000
|
||||
input_cost_per_token: 0.0000016
|
||||
output_cost_per_token: 0.0000128
|
||||
|
||||
- model_code: deepseek-chat
|
||||
factory_name: deepseek
|
||||
pricing_tiers:
|
||||
- max_prompt_tokens: 128000
|
||||
input_cost_per_token: 0.000002
|
||||
output_cost_per_token: 0.000003
|
||||
cache_hit_cost_per_token: 0.0000002
|
||||
|
||||
- model_code: qwen3.5-27b
|
||||
factory_name: dashscope
|
||||
pricing_tiers:
|
||||
- max_prompt_tokens: 128000
|
||||
input_cost_per_token: 0.0000006
|
||||
output_cost_per_token: 0.0000048
|
||||
- max_prompt_tokens: 256000
|
||||
input_cost_per_token: 0.0000018
|
||||
output_cost_per_token: 0.0000144
|
||||
@@ -0,0 +1,28 @@
|
||||
agents:
|
||||
- agent_type: router
|
||||
llm_model_code: qwen3.5-flash
|
||||
status: active
|
||||
config:
|
||||
temperature: 0.7
|
||||
max_tokens: null
|
||||
timeout_seconds: 30
|
||||
context_messages:
|
||||
mode: day
|
||||
count: 2
|
||||
enabled_tools: []
|
||||
|
||||
- agent_type: worker
|
||||
llm_model_code: qwen3.5-flash
|
||||
status: active
|
||||
config:
|
||||
temperature: 0.7
|
||||
max_tokens: null
|
||||
timeout_seconds: 30
|
||||
context_messages:
|
||||
mode: number
|
||||
count: 20
|
||||
enabled_tools:
|
||||
- calendar.read
|
||||
- calendar.write
|
||||
- calendar.share
|
||||
- user.lookup
|
||||
@@ -0,0 +1,158 @@
|
||||
version: "1.0"
|
||||
routes:
|
||||
- route_id: auth.boot
|
||||
path: /boot
|
||||
description: Bootstraps auth session and redirects to login or home.
|
||||
category: auth
|
||||
auth_required: false
|
||||
- route_id: auth.login
|
||||
path: /login
|
||||
description: Login entry for unauthenticated users.
|
||||
category: auth
|
||||
auth_required: false
|
||||
- route_id: home.main
|
||||
path: /
|
||||
description: Main assistant home screen.
|
||||
category: home
|
||||
auth_required: true
|
||||
- route_id: message.invite_list
|
||||
path: /messages/invites
|
||||
description: Lists message invitations.
|
||||
category: messages
|
||||
auth_required: true
|
||||
- route_id: message.invite_detail
|
||||
path: /messages/invites/{id}
|
||||
description: Shows details for a single invitation.
|
||||
category: messages
|
||||
auth_required: true
|
||||
path_params:
|
||||
- id
|
||||
- route_id: contacts.list
|
||||
path: /contacts
|
||||
description: Contact list and quick relationship actions.
|
||||
category: contacts
|
||||
auth_required: true
|
||||
- route_id: contacts.add
|
||||
path: /contacts/add
|
||||
description: Create or edit a contact profile.
|
||||
category: contacts
|
||||
auth_required: true
|
||||
- route_id: calendar.dayweek
|
||||
path: /calendar/dayweek
|
||||
description: Day and week calendar view.
|
||||
category: calendar
|
||||
auth_required: true
|
||||
query_params:
|
||||
- date
|
||||
- from
|
||||
- route_id: calendar.month
|
||||
path: /calendar/month
|
||||
description: Month calendar overview.
|
||||
category: calendar
|
||||
auth_required: true
|
||||
query_params:
|
||||
- from
|
||||
- route_id: calendar.event_detail
|
||||
path: /calendar/events/{id}
|
||||
description: Detail page for one calendar event.
|
||||
category: calendar
|
||||
auth_required: true
|
||||
path_params:
|
||||
- id
|
||||
- route_id: calendar.event_create
|
||||
path: /calendar/events/new
|
||||
description: Create page for one calendar event.
|
||||
category: calendar
|
||||
auth_required: true
|
||||
query_params:
|
||||
- date
|
||||
- route_id: calendar.event_edit
|
||||
path: /calendar/events/{id}/edit
|
||||
description: Edit page for one calendar event.
|
||||
category: calendar
|
||||
auth_required: true
|
||||
path_params:
|
||||
- id
|
||||
- route_id: calendar.event_share
|
||||
path: /calendar/events/{id}/share
|
||||
description: Share settings page for one calendar event.
|
||||
category: calendar
|
||||
auth_required: true
|
||||
path_params:
|
||||
- id
|
||||
- route_id: todo.list
|
||||
path: /todo
|
||||
description: Todo quadrants and backlog overview.
|
||||
category: todo
|
||||
auth_required: true
|
||||
- route_id: todo.create
|
||||
path: /todo/new
|
||||
description: Create page for one todo item.
|
||||
category: todo
|
||||
auth_required: true
|
||||
- route_id: todo.detail
|
||||
path: /todo/{id}
|
||||
description: Detail page for one todo item.
|
||||
category: todo
|
||||
auth_required: true
|
||||
path_params:
|
||||
- id
|
||||
- route_id: todo.edit
|
||||
path: /todo/{id}/edit
|
||||
description: Dedicated subpage for editing one todo item (not an in-page modal).
|
||||
category: todo
|
||||
auth_required: true
|
||||
path_params:
|
||||
- id
|
||||
- route_id: settings.main
|
||||
path: /settings
|
||||
description: Settings hub page.
|
||||
category: settings
|
||||
auth_required: true
|
||||
- route_id: settings.features
|
||||
path: /settings/features
|
||||
description: Automation job list page.
|
||||
category: settings
|
||||
auth_required: true
|
||||
- route_id: settings.job_new
|
||||
path: /settings/job/new
|
||||
description: Create page for one automation job.
|
||||
category: settings
|
||||
auth_required: true
|
||||
- route_id: settings.job_detail
|
||||
path: /settings/job/{id}
|
||||
description: Detail page for one automation job.
|
||||
category: settings
|
||||
auth_required: true
|
||||
path_params:
|
||||
- id
|
||||
- route_id: settings.memory
|
||||
path: /settings/memory
|
||||
description: Memory preferences and controls.
|
||||
category: settings
|
||||
auth_required: true
|
||||
- route_id: settings.memory_user
|
||||
path: /settings/memory/user
|
||||
description: User memory summary view.
|
||||
category: settings
|
||||
auth_required: true
|
||||
- route_id: settings.memory_work
|
||||
path: /settings/memory/work
|
||||
description: Work memory summary view.
|
||||
category: settings
|
||||
auth_required: true
|
||||
- route_id: settings.memory_user_edit
|
||||
path: /settings/memory/user/edit
|
||||
description: Edit user memory details.
|
||||
category: settings
|
||||
auth_required: true
|
||||
- route_id: settings.memory_work_edit
|
||||
path: /settings/memory/work/edit
|
||||
description: Edit work memory details.
|
||||
category: settings
|
||||
auth_required: true
|
||||
- route_id: settings.edit_profile
|
||||
path: /edit-profile
|
||||
description: Profile editing page.
|
||||
category: settings
|
||||
auth_required: true
|
||||
Reference in New Issue
Block a user