myagent-ai 1.15.78 → 1.15.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -280,8 +280,9 @@ class MainAgent(BaseAgent):
280
280
  return None
281
281
 
282
282
  from datetime import datetime
283
+ from core.utils import get_config_tz
283
284
  old_time = old_memory.created_at or "未知时间"
284
- new_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
285
+ new_time = datetime.now(get_config_tz()).strftime("%Y-%m-%d %H:%M:%S")
285
286
  user_msg = context.user_message or ""
286
287
 
287
288
  merge_prompt = f"""你是一个记忆管理系统。现在系统检测到两条高度相似的记忆,请你判断如何合并它们。
@@ -377,7 +378,7 @@ class MainAgent(BaseAgent):
377
378
  safe_session = session_id.replace("-", "").replace("/", "_")[:12] if session_id else "default"
378
379
  kb_file = auto_kb_dir / f"{safe_session}.md"
379
380
 
380
- now_str = datetime.now().strftime("%Y-%m-%d %H:%M")
381
+ now_str = datetime.now(get_config_tz()).strftime("%Y-%m-%d %H:%M")
381
382
 
382
383
  # 检查重复:与已有文件内容做相似度比较
383
384
  existing_content = ""
package/config.py CHANGED
@@ -150,6 +150,7 @@ class AppConfig:
150
150
  log_level: str = "INFO"
151
151
  data_dir: str = "" # 数据目录,默认 ~/.myagent/
152
152
  language: str = "zh-CN"
153
+ timezone: str = "Asia/Shanghai" # 时区,用于生成时间戳和提示词中的当前时间
153
154
 
154
155
 
155
156
  # ==============================================================================
@@ -191,9 +191,12 @@ class ContextBuilder:
191
191
  """
192
192
  构建 <datetime> 段落 —— 当前日期时间(精确到秒)。
193
193
  让 LLM 知道当前时间,以便给出与时间相关的回答。
194
+ 使用配置的时区,而非系统时区。
194
195
  """
196
+ from core.utils import get_config_tz
195
197
  from datetime import datetime
196
- now = datetime.now()
198
+ tz = get_config_tz()
199
+ now = datetime.now(tz)
197
200
  weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
198
201
  date_str = now.strftime("%Y年%m月%d日")
199
202
  time_str = now.strftime("%H:%M:%S")
@@ -201,7 +204,7 @@ class ContextBuilder:
201
204
  return (
202
205
  f"<datetime>\n"
203
206
  f"当前时间: {date_str} {weekday} {time_str}\n"
204
- f"时间戳: {now.timestamp()}\n"
207
+ f"时区: {tz}\n"
205
208
  f"</datetime>"
206
209
  )
207
210
 
package/core/utils.py CHANGED
@@ -9,14 +9,30 @@ import uuid
9
9
  import time
10
10
  import re
11
11
  from datetime import datetime, timezone
12
+ from zoneinfo import ZoneInfo
12
13
  from typing import Any, Dict, Optional, TypeVar
13
14
 
14
15
  T = TypeVar("T")
15
16
 
17
+ # 时区缓存,避免每次调用都重新解析
18
+ _tz_cache: Optional[ZoneInfo] = None
19
+
20
+ def get_config_tz() -> ZoneInfo:
21
+ """获取配置的时区对象(带缓存)"""
22
+ global _tz_cache
23
+ if _tz_cache is not None:
24
+ return _tz_cache
25
+ try:
26
+ from config import ConfigManager
27
+ tz_name = ConfigManager().get("timezone", "Asia/Shanghai")
28
+ _tz_cache = ZoneInfo(tz_name)
29
+ except Exception:
30
+ _tz_cache = ZoneInfo("Asia/Shanghai")
31
+ return _tz_cache
16
32
 
17
33
  def timestamp() -> str:
18
- """返回 ISO 8601 格式时间戳(本地时间)"""
19
- return datetime.now().isoformat()
34
+ """返回 ISO 8601 格式时间戳(使用配置时区)"""
35
+ return datetime.now(get_config_tz()).isoformat()
20
36
 
21
37
 
22
38
  def timestamp_ms() -> int:
package/memory/manager.py CHANGED
@@ -473,7 +473,8 @@ class MemoryManager:
473
473
  def add_global(self, session_id="global", key="", content="", summary="", importance=0.7, metadata=None) -> str:
474
474
  """添加全局记忆(跨会话可检索)"""
475
475
  from datetime import datetime
476
- now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
476
+ from core.utils import get_config_tz
477
+ now_str = datetime.now(get_config_tz()).strftime("%Y-%m-%d %H:%M:%S")
477
478
  ts_summary = summary or truncate_str(content, 200)
478
479
  entry = MemoryEntry(
479
480
  session_id=session_id, category="global", key=key,
@@ -676,7 +677,7 @@ class MemoryManager:
676
677
  try:
677
678
  # 解析 ISO 8601 时间戳
678
679
  created_dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
679
- now_dt = datetime.now(created_dt.tzinfo) if created_dt.tzinfo else datetime.now()
680
+ now_dt = datetime.now(get_config_tz())
680
681
 
681
682
  age_seconds = (now_dt - created_dt).total_seconds()
682
683
  if age_seconds <= 0:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.15.78",
3
+ "version": "1.15.80",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/api_server.py CHANGED
@@ -18,6 +18,11 @@ from web.tts_handler import synthesize, preprocess_for_tts, AVAILABLE_VOICES
18
18
 
19
19
  logger = get_logger("myagent.api")
20
20
 
21
+ def _now_iso():
22
+ """返回配置时区的 ISO 时间戳"""
23
+ from core.utils import get_config_tz
24
+ return datetime.datetime.now(get_config_tz()).isoformat()
25
+
21
26
  def _safe_load_json(filepath, default=None):
22
27
  """安全读取 JSON 文件,解析失败返回默认值"""
23
28
  try:
@@ -339,6 +344,8 @@ class ApiServer:
339
344
  r.add_post("/api/knowledge/search", self.handle_knowledge_search)
340
345
  # ── 配置管理 (热重载/导入/导出) ──
341
346
  r.add_get("/api/config", self.handle_get_config)
347
+ r.add_post("/api/config/get", self.handle_get_config_key)
348
+ r.add_post("/api/config/set", self.handle_set_config_key)
342
349
  r.add_post("/api/config/reload", self.handle_reload_config)
343
350
  r.add_post("/api/config/export", self.handle_export_config)
344
351
  r.add_post("/api/config/import", self.handle_import_config)
@@ -1619,7 +1626,7 @@ class ApiServer:
1619
1626
  # 迁移后再次检查(损坏文件可能已被删除)
1620
1627
  if not (ad / "config.json").exists():
1621
1628
  ad.mkdir(parents=True, exist_ok=True)
1622
- now = datetime.datetime.now().isoformat()
1629
+ now = _now_iso()
1623
1630
  cfg = {
1624
1631
  "id": uuid.uuid4().hex[:12],
1625
1632
  "name": "全权Agent",
@@ -1667,7 +1674,7 @@ class ApiServer:
1667
1674
  # 其他 agent:创建一个最小 config
1668
1675
  ad = self._agent_dir(path)
1669
1676
  if ad.exists():
1670
- now = datetime.datetime.now().isoformat()
1677
+ now = _now_iso()
1671
1678
  cfg = {
1672
1679
  "id": uuid.uuid4().hex[:12],
1673
1680
  "name": path,
@@ -1682,7 +1689,7 @@ class ApiServer:
1682
1689
  logger.info(f"已重建 Agent 配置: {path}")
1683
1690
  return
1684
1691
  changed = False
1685
- now = datetime.datetime.now().isoformat()
1692
+ now = _now_iso()
1686
1693
  if "id" not in cfg:
1687
1694
  cfg["id"] = uuid.uuid4().hex[:12]
1688
1695
  changed = True
@@ -1708,7 +1715,7 @@ class ApiServer:
1708
1715
  # 迁移后再次检查(损坏文件可能已被删除)
1709
1716
  if not (ad / "config.json").exists():
1710
1717
  ad.mkdir(parents=True, exist_ok=True)
1711
- now = datetime.datetime.now().isoformat()
1718
+ now = _now_iso()
1712
1719
  cfg = {
1713
1720
  "id": uuid.uuid4().hex[:12],
1714
1721
  "name": "配置助手",
@@ -1737,7 +1744,7 @@ class ApiServer:
1737
1744
  cfg = json.loads((ad / "config.json").read_text(encoding="utf-8"))
1738
1745
  if cfg.get("system_prompt") != CONFIG_HELPER_PROMPT:
1739
1746
  cfg["system_prompt"] = CONFIG_HELPER_PROMPT
1740
- cfg["updated_at"] = datetime.datetime.now().isoformat()
1747
+ cfg["updated_at"] = _now_iso()
1741
1748
  (ad / "config.json").write_text(json.dumps(cfg, indent=2, ensure_ascii=False), encoding="utf-8")
1742
1749
  logger.info("已同步配置助手最新的系统提示词")
1743
1750
  except Exception as e:
@@ -1962,7 +1969,7 @@ class ApiServer:
1962
1969
  if (ad / "config.json").exists():
1963
1970
  return web.json_response({"error": f"Agent '{name}' already exists"}, status=409)
1964
1971
 
1965
- now = datetime.datetime.now().isoformat()
1972
+ now = _now_iso()
1966
1973
  cfg = {
1967
1974
  "id": uuid.uuid4().hex[:12],
1968
1975
  "name": name,
@@ -2186,7 +2193,7 @@ class ApiServer:
2186
2193
  if "system" in data:
2187
2194
  del data["system"]
2188
2195
  # 自动更新 updated_at
2189
- cfg["updated_at"] = datetime.datetime.now().isoformat()
2196
+ cfg["updated_at"] = _now_iso()
2190
2197
  (ad / "config.json").write_text(json.dumps(cfg, indent=2, ensure_ascii=False), encoding="utf-8")
2191
2198
  # 部门变更时同步部门成员列表(old_dept 已在 cfg 修改前保存)
2192
2199
  new_dept = data.get("department", old_dept)
@@ -3316,6 +3323,28 @@ class ApiServer:
3316
3323
  cfg = self.core.config_mgr.get_full_config()
3317
3324
  return web.json_response(cfg)
3318
3325
 
3326
+ async def handle_get_config_key(self, request):
3327
+ """POST /api/config/get - 获取单个配置项"""
3328
+ data = await request.json()
3329
+ key = data.get("key", "")
3330
+ value = getattr(self.core.config, key, None)
3331
+ return web.json_response({"ok": True, "value": value or ""})
3332
+
3333
+ async def handle_set_config_key(self, request):
3334
+ """POST /api/config/set - 设置单个配置项并保存"""
3335
+ data = await request.json()
3336
+ key = data.get("key", "")
3337
+ value = data.get("value", "")
3338
+ if not key:
3339
+ return web.json_response({"ok": False, "error": "缺少 key"})
3340
+ setattr(self.core.config, key, value)
3341
+ self.core.config_mgr.save()
3342
+ # 时区变更时清除缓存,下次调用 get_config_tz() 立即生效
3343
+ if key == "timezone":
3344
+ import core.utils as _u
3345
+ _u._tz_cache = None
3346
+ return web.json_response({"ok": True})
3347
+
3319
3348
  def _build_model_chain(self, agent_cfg: dict | None, agent_path: str) -> list[dict]:
3320
3349
  """构建模型链: [主模型, 备用模型1, 备用模型2, ...]"""
3321
3350
  if not agent_cfg:
@@ -4697,8 +4726,9 @@ class ApiServer:
4697
4726
  include_secrets = data.get("include_secrets", False)
4698
4727
 
4699
4728
  try:
4729
+ from core.utils import get_config_tz
4700
4730
  export_data = self.core.config_mgr.export_config(include_secrets=include_secrets)
4701
- filename = f"myagent_config_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
4731
+ filename = f"myagent_config_{datetime.datetime.now(get_config_tz()).strftime('%Y%m%d_%H%M%S')}.json"
4702
4732
 
4703
4733
  resp = web.Response(
4704
4734
  body=json.dumps(export_data, ensure_ascii=False, indent=2),
@@ -6221,7 +6251,7 @@ class ApiServer:
6221
6251
  self._execution_lock = {
6222
6252
  "locked": True,
6223
6253
  "locked_by": agent_path,
6224
- "locked_at": datetime.datetime.now().isoformat(),
6254
+ "locked_at": _now_iso(),
6225
6255
  }
6226
6256
  logger.info(f"全局执行锁已获取: {agent_path}")
6227
6257
  return web.json_response({
package/web/ui/index.html CHANGED
@@ -2005,10 +2005,13 @@ async function uploadDeptKB(path,folderMode){
2005
2005
  async function renderSystem(){
2006
2006
  $('content').innerHTML=`
2007
2007
  <div class="card">
2008
- <h3>🔄 热重载</h3>
2009
- <p style="font-size:13px;color:var(--text2);margin-bottom:12px">从配置文件重新加载所有设置,立即生效,无需重启服务</p>
2010
- <button class="btn btn-primary" id="sysReloadBtn" onclick="sysHotReload()">热重载配置</button>
2011
- <div id="sysReloadMsg"></div>
2008
+ <h3>🕐 时区设置</h3>
2009
+ <p style="font-size:13px;color:var(--text2);margin-bottom:12px">设置系统时区,影响提示词中的当前时间、记忆时间戳、日志时间等所有时间显示</p>
2010
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
2011
+ <select id="sysTimezone" style="flex:1;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);color:var(--text);font-size:14px"></select>
2012
+ <button class="btn btn-primary" id="sysTzSaveBtn" onclick="sysSaveTimezone()">保存</button>
2013
+ </div>
2014
+ <div id="sysTzMsg"></div>
2012
2015
  </div>
2013
2016
  <div class="card">
2014
2017
  <h3>📤 导出备份</h3>
@@ -2038,31 +2041,38 @@ async function renderSystem(){
2038
2041
  <h3>👁️ 配置预览</h3>
2039
2042
  <div class="config-preview" id="sysConfigPreview">加载中...</div>
2040
2043
  </div>`;
2044
+ sysLoadTimezone();
2041
2045
  sysLoadPreview();
2042
2046
  }
2043
2047
 
2044
- function sysSetMsg(id,type,msg){
2045
- const el=$(id);if(!el)return;
2046
- el.className='status-msg '+type;el.textContent=msg;
2047
- if(type!=='loading')setTimeout(()=>{if(el.className.includes(type))el.className='status-msg';},8000);
2048
+ const COMMON_TIMEZONES=['Asia/Shanghai','Asia/Kuala_Lumpur','Asia/Singapore','Asia/Tokyo','Asia/Seoul','Asia/Ho_Chi_Minh','Asia/Bangkok','Asia/Jakarta','Asia/Kolkata','Asia/Dubai','Europe/London','Europe/Paris','Europe/Berlin','Europe/Moscow','America/New_York','America/Chicago','America/Denver','America/Los_Angeles','Australia/Sydney','Pacific/Auckland','UTC'];
2049
+
2050
+ async function sysLoadTimezone(){
2051
+ try{
2052
+ const r=await api('/api/config/get',{method:'POST',body:JSON.stringify({key:'timezone'})});
2053
+ const sel=$('sysTimezone');
2054
+ sel.innerHTML=COMMON_TIMEZONES.map(tz=>`<option value="${tz}"${tz===(r.value||'Asia/Shanghai')?' selected':''}>${tz}</option>`).join('');
2055
+ }catch(e){console.error('Load timezone failed:',e);}
2048
2056
  }
2049
2057
 
2050
- async function sysHotReload(){
2051
- const btn=$('sysReloadBtn');btn.disabled=true;
2052
- sysSetMsg('sysReloadMsg','loading','正在热重载配置...');
2058
+ async function sysSaveTimezone(){
2059
+ const btn=$('sysTzSaveBtn');btn.disabled=true;
2060
+ sysSetMsg('sysTzMsg','loading','正在保存时区...');
2053
2061
  try{
2054
- const r=await api('/api/config/reload',{method:'POST',body:JSON.stringify({})});
2055
- if(r.ok){
2056
- const changes=r.changes||[];
2057
- let msg=' 配置已热重载';
2058
- msg+=changes.length>0?' — 变更: '+changes.join(', '):' 无显著变更';
2059
- sysSetMsg('sysReloadMsg','success',msg);showToast(msg,'success');
2060
- sysLoadPreview();
2061
- }else{sysSetMsg('sysReloadMsg','error','❌ 热重载失败: '+(r.error||'未知错误'));}
2062
- }catch(e){sysSetMsg('sysReloadMsg','error','❌ 热重载失败: '+e.message);}
2062
+ const tz=$('sysTimezone').value;
2063
+ const r=await api('/api/config/set',{method:'POST',body:JSON.stringify({key:'timezone',value:tz})});
2064
+ if(r.ok){sysSetMsg('sysTzMsg','success','✅ 时区已保存为 '+tz);showToast('时区已更新为 '+tz,'success');}
2065
+ else{sysSetMsg('sysTzMsg','error','❌ 保存失败: '+(r.error||'未知错误'));}
2066
+ }catch(e){sysSetMsg('sysTzMsg','error',' 保存失败: '+e.message);}
2063
2067
  btn.disabled=false;
2064
2068
  }
2065
2069
 
2070
+ function sysSetMsg(id,type,msg){
2071
+ const el=$(id);if(!el)return;
2072
+ el.className='status-msg '+type;el.textContent=msg;
2073
+ if(type!=='loading')setTimeout(()=>{if(el.className.includes(type))el.className='status-msg';},8000);
2074
+ }
2075
+
2066
2076
  async function sysExport(includeSecrets){
2067
2077
  const btnId=includeSecrets?'sysExportFull':'sysExportSafe';
2068
2078
  const btn=$(btnId);btn.disabled=true;