session-trail 0.2.0__tar.gz → 0.2.2__tar.gz

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.
Files changed (43) hide show
  1. {session_trail-0.2.0/src/session_trail.egg-info → session_trail-0.2.2}/PKG-INFO +5 -3
  2. {session_trail-0.2.0 → session_trail-0.2.2}/README.en.md +4 -2
  3. {session_trail-0.2.0 → session_trail-0.2.2}/README.md +4 -2
  4. {session_trail-0.2.0 → session_trail-0.2.2}/pyproject.toml +1 -1
  5. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/cli.py +1 -0
  6. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/tray/app.py +89 -4
  7. {session_trail-0.2.0 → session_trail-0.2.2/src/session_trail.egg-info}/PKG-INFO +5 -3
  8. {session_trail-0.2.0 → session_trail-0.2.2}/LICENSE +0 -0
  9. {session_trail-0.2.0 → session_trail-0.2.2}/MANIFEST.in +0 -0
  10. {session_trail-0.2.0 → session_trail-0.2.2}/setup.cfg +0 -0
  11. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/__init__.py +0 -0
  12. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/__main__.py +0 -0
  13. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/__init__.py +0 -0
  14. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/base.py +0 -0
  15. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/claude_code.py +0 -0
  16. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/codebuddy.py +0 -0
  17. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/codex.py +0 -0
  18. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/deepseek_harness.py +0 -0
  19. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/jsonl_utils.py +0 -0
  20. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/kimi_code.py +0 -0
  21. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/minimax_code.py +0 -0
  22. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/opencode.py +0 -0
  23. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/pi.py +0 -0
  24. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/sqlite_utils.py +0 -0
  25. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/adapters/zcode.py +0 -0
  26. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/launcher.py +0 -0
  27. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/models.py +0 -0
  28. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/scanner.py +0 -0
  29. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/server.py +0 -0
  30. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/static/app.js +0 -0
  31. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/static/i18n.js +0 -0
  32. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/static/index.html +0 -0
  33. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/static/styles.css +0 -0
  34. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/store.py +0 -0
  35. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/tray/__init__.py +0 -0
  36. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/tray/desktop.template +0 -0
  37. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/tray/icon.png +0 -0
  38. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/tray/sni.py +0 -0
  39. {session_trail-0.2.0 → session_trail-0.2.2}/src/agent_trail/watcher.py +0 -0
  40. {session_trail-0.2.0 → session_trail-0.2.2}/src/session_trail.egg-info/SOURCES.txt +0 -0
  41. {session_trail-0.2.0 → session_trail-0.2.2}/src/session_trail.egg-info/dependency_links.txt +0 -0
  42. {session_trail-0.2.0 → session_trail-0.2.2}/src/session_trail.egg-info/entry_points.txt +0 -0
  43. {session_trail-0.2.0 → session_trail-0.2.2}/src/session_trail.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: session-trail
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Local cross-agent work timeline: discover, browse and resume coding-agent sessions on your machine
5
5
  Author: fwt11
6
6
  License-Expression: MIT
@@ -101,9 +101,11 @@ agent-trail serve --interval 30 # 轮询间隔(watch 健康时自动降
101
101
  ```bash
102
102
  agent-trail tray # 启动托盘(自动拉起 agent-trail serve)
103
103
  agent-trail tray --show # 启动同时打开控制台窗口
104
- agent-trail tray --install # 写入应用菜单入口;--uninstall 反向清理
104
+ agent-trail tray --install # 手动写入应用菜单入口;--uninstall 反向清理
105
105
  ```
106
106
 
107
+ pip 安装后首次运行 `agent-trail tray` 会自动把"Agent 足迹"注册进系统应用菜单(应用启动器可直接搜索到),之后升级换路径也会自动刷新;无桌面会话(SSH/服务器)不写入。
108
+
107
109
  托盘壳本身不依赖任何第三方 pip 包,但需要 GTK 的 GI 绑定(系统 Python 通常自带)。`agent-trail tray` 在当前解释器缺 GI 绑定时会自动改用带绑定的系统 Python 重执行(例如 pip 装在 venv 里、托盘跑在系统 Python),无需任何手动操作;实在找不到时按提示安装:
108
110
 
109
111
  ```bash
@@ -111,7 +113,7 @@ agent-trail tray --install # 写入应用菜单入口;--uninstall 反向清
111
113
  sudo apt install python3-gi gir1.2-webkit2-4.1
112
114
  ```
113
115
 
114
- 托盘以子进程方式管理 `agent-trail serve`(可用环境变量 `AGENT_TRAIL_BIN` 覆盖可执行文件路径),服务异常退出自动重启(退避重试,连续失败后停在可手动启动的状态)。窗口直接关闭只是隐藏,退出走托盘菜单;flock 单实例锁防止重复启动。
116
+ `agent-trail tray` 默认脱离终端后台运行:命令立即返回,日志追加在 `~/.agent-trail/tray.log`,关闭终端不影响托盘;`--foreground` 可前台运行调试。托盘以子进程方式管理 `agent-trail serve`——**启动托盘即自动拉起服务**(菜单里可停止/启动,异常退出自动重启)。窗口直接关闭只是隐藏,退出走托盘菜单,或 `kill $(cat ~/.agent-trail/tray.lock)`;flock 单实例锁防止重复启动(重复执行会提示"已在运行")。
115
117
 
116
118
  托盘图标走原生 StatusNotifierItem/DBusMenu 协议(`agent_trail/tray/sni.py`,Gio 实现——libayatana 在 Deepin 23 的托盘守护上不工作,详见源码注释)。内嵌 WebView 默认使用软件合成(`WEBKIT_DISABLE_DMABUF_RENDERER` / `WEBKIT_DISABLE_COMPOSITING_MODE`,可用同名环境变量覆盖),避免在无 DRM 权限的会话里出现空白窗口。`serve` 的所有命令行形态保持不变,无托盘环境(SSH/无桌面)不受影响。
117
119
 
@@ -74,9 +74,11 @@ Beyond `serve`, a tray application embeds the console in a native window and add
74
74
  ```bash
75
75
  agent-trail tray # start the tray (spawns agent-trail serve)
76
76
  agent-trail tray --show # also open the console window
77
- agent-trail tray --install # add an applications-menu entry; --uninstall reverts
77
+ agent-trail tray --install # manually add an applications-menu entry; --uninstall reverts
78
78
  ```
79
79
 
80
+ The first `agent-trail tray` run after pip install automatically registers "Agent Trail" in the desktop applications menu (searchable from your launcher) and refreshes it on upgrades; headless sessions (SSH/servers) write nothing.
81
+
80
82
  The tray needs GTK GI bindings (preinstalled on most distros as the system Python). If the interpreter running agent-trail lacks them — e.g. the package lives in a venv — `agent-trail tray` automatically re-executes itself with a system Python that has GI; no manual steps. If none is found, install:
81
83
 
82
84
  ```bash
@@ -84,7 +86,7 @@ The tray needs GTK GI bindings (preinstalled on most distros as the system Pytho
84
86
  sudo apt install python3-gi gir1.2-webkit2-4.1
85
87
  ```
86
88
 
87
- The tray manages `agent-trail serve` as a subprocess (override with `AGENT_TRAIL_BIN`) and restarts it automatically with backoff if it dies. Closing the window only hides it; quit lives in the tray menu. A flock single-instance lock prevents duplicates.
89
+ `agent-trail tray` detaches from the terminal by default: the command returns immediately, logs append to `~/.agent-trail/tray.log`, and closing the terminal does not affect the tray; use `--foreground` to debug in the foreground. The tray manages `agent-trail serve` as a subprocess — **starting the tray starts the service** (stop/start from the menu; automatic restart with backoff if it dies). Closing the window only hides it; quit lives in the tray menu, or `kill $(cat ~/.agent-trail/tray.lock)`. A flock single-instance lock prevents duplicates (a second invocation prints "already running").
88
90
 
89
91
  The tray icon speaks the native StatusNotifierItem/DBusMenu protocol (`agent_trail/tray/sni.py`, implemented with Gio — libayatana does not work against Deepin 23's tray daemon; see source comments). The embedded WebView defaults to software compositing (`WEBKIT_DISABLE_DMABUF_RENDERER` / `WEBKIT_DISABLE_COMPOSITING_MODE`, overridable via env), avoiding blank windows on sessions without DRM access. All `serve`/CLI forms keep working without a desktop.
90
92
 
@@ -75,9 +75,11 @@ agent-trail serve --interval 30 # 轮询间隔(watch 健康时自动降
75
75
  ```bash
76
76
  agent-trail tray # 启动托盘(自动拉起 agent-trail serve)
77
77
  agent-trail tray --show # 启动同时打开控制台窗口
78
- agent-trail tray --install # 写入应用菜单入口;--uninstall 反向清理
78
+ agent-trail tray --install # 手动写入应用菜单入口;--uninstall 反向清理
79
79
  ```
80
80
 
81
+ pip 安装后首次运行 `agent-trail tray` 会自动把"Agent 足迹"注册进系统应用菜单(应用启动器可直接搜索到),之后升级换路径也会自动刷新;无桌面会话(SSH/服务器)不写入。
82
+
81
83
  托盘壳本身不依赖任何第三方 pip 包,但需要 GTK 的 GI 绑定(系统 Python 通常自带)。`agent-trail tray` 在当前解释器缺 GI 绑定时会自动改用带绑定的系统 Python 重执行(例如 pip 装在 venv 里、托盘跑在系统 Python),无需任何手动操作;实在找不到时按提示安装:
82
84
 
83
85
  ```bash
@@ -85,7 +87,7 @@ agent-trail tray --install # 写入应用菜单入口;--uninstall 反向清
85
87
  sudo apt install python3-gi gir1.2-webkit2-4.1
86
88
  ```
87
89
 
88
- 托盘以子进程方式管理 `agent-trail serve`(可用环境变量 `AGENT_TRAIL_BIN` 覆盖可执行文件路径),服务异常退出自动重启(退避重试,连续失败后停在可手动启动的状态)。窗口直接关闭只是隐藏,退出走托盘菜单;flock 单实例锁防止重复启动。
90
+ `agent-trail tray` 默认脱离终端后台运行:命令立即返回,日志追加在 `~/.agent-trail/tray.log`,关闭终端不影响托盘;`--foreground` 可前台运行调试。托盘以子进程方式管理 `agent-trail serve`——**启动托盘即自动拉起服务**(菜单里可停止/启动,异常退出自动重启)。窗口直接关闭只是隐藏,退出走托盘菜单,或 `kill $(cat ~/.agent-trail/tray.lock)`;flock 单实例锁防止重复启动(重复执行会提示"已在运行")。
89
91
 
90
92
  托盘图标走原生 StatusNotifierItem/DBusMenu 协议(`agent_trail/tray/sni.py`,Gio 实现——libayatana 在 Deepin 23 的托盘守护上不工作,详见源码注释)。内嵌 WebView 默认使用软件合成(`WEBKIT_DISABLE_DMABUF_RENDERER` / `WEBKIT_DISABLE_COMPOSITING_MODE`,可用同名环境变量覆盖),避免在无 DRM 权限的会话里出现空白窗口。`serve` 的所有命令行形态保持不变,无托盘环境(SSH/无桌面)不受影响。
91
93
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "session-trail"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Local cross-agent work timeline: discover, browse and resume coding-agent sessions on your machine"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -31,6 +31,7 @@ def build_parser() -> argparse.ArgumentParser:
31
31
  serve.add_argument("--no-watch", action="store_true")
32
32
  tray = commands.add_parser("tray", help="system tray app (Linux)")
33
33
  tray.add_argument("--show", action="store_true", help="open the console window at startup")
34
+ tray.add_argument("--foreground", action="store_true", help="run in the foreground (default: detach with logs in ~/.agent-trail/tray.log)")
34
35
  tray.add_argument("--install", action="store_true", help="add an applications-menu entry")
35
36
  tray.add_argument("--uninstall", action="store_true", help="remove the menu entry and autostart")
36
37
  return parser
@@ -96,6 +96,7 @@ def tr(key: str, **params) -> str:
96
96
 
97
97
 
98
98
  LOCK_PATH = Path.home() / ".agent-trail" / "tray.lock"
99
+ LOG_PATH = Path.home() / ".agent-trail" / "tray.log"
99
100
  AUTOSTART_PATH = Path.home() / ".config" / "autostart" / "agent-trail-tray.desktop"
100
101
  APP_MENU_PATH = Path.home() / ".local" / "share" / "applications" / "agent-trail-tray.desktop"
101
102
 
@@ -111,6 +112,11 @@ def emit(event: dict) -> None:
111
112
  print(json.dumps(event, ensure_ascii=False), flush=True)
112
113
 
113
114
 
115
+ def _looks_executable(path: str) -> bool:
116
+ candidate = Path(path)
117
+ return candidate.is_file() and os.access(candidate, os.X_OK)
118
+
119
+
114
120
  class TrailCore:
115
121
  """平台无关的服务子进程管理器;不含任何 GUI 依赖。
116
122
 
@@ -128,9 +134,15 @@ class TrailCore:
128
134
  restart_delay: float = 3.0,
129
135
  stability_seconds: float = 300.0,
130
136
  ) -> None:
137
+ # serve 的拉起命令不依赖 PATH:executable 未显式给出(或给出的路径
138
+ # 无效)时,退到 [sys.executable, -m, agent_trail]——托盘进程自己能
139
+ # 跑,serve 就能跑(重执行场景的 PYTHONPATH 会被子进程继承)。
131
140
  self.executable = executable
132
- if self.executable is None:
133
- self.executable = os.environ.get("AGENT_TRAIL_BIN") or shutil.which("agent-trail")
141
+ if self.executable is not None and not _looks_executable(self.executable):
142
+ self.executable = None
143
+ self._env_bin = os.environ.get("AGENT_TRAIL_BIN")
144
+ if self._env_bin is not None and not _looks_executable(self._env_bin):
145
+ self._env_bin = None
134
146
  self.on_event = on_event or (lambda event: None)
135
147
  self.max_restarts = max_restarts
136
148
  self.restart_delay = restart_delay
@@ -149,7 +161,13 @@ class TrailCore:
149
161
 
150
162
  @property
151
163
  def available(self) -> bool:
152
- return bool(self.executable)
164
+ return True # 至少有 [sys.executable, -m agent_trail] 兜底
165
+
166
+ def _serve_command(self) -> list[str]:
167
+ for candidate in (self.executable, self._env_bin):
168
+ if candidate and _looks_executable(candidate):
169
+ return [candidate, "serve", "--no-browser"]
170
+ return [sys.executable, "-m", "agent_trail", "serve", "--no-browser"]
153
171
 
154
172
  def start(self, *, reset_attempts: bool = True) -> bool:
155
173
  with self._lock:
@@ -166,7 +184,7 @@ class TrailCore:
166
184
  self.url = self.port = self.key = None
167
185
  self._spawned_at = time.monotonic()
168
186
  self.process = subprocess.Popen(
169
- [self.executable, "serve", "--no-browser"],
187
+ self._serve_command(),
170
188
  stdout=subprocess.PIPE,
171
189
  stderr=subprocess.STDOUT,
172
190
  text=True,
@@ -287,6 +305,38 @@ class TrailCore:
287
305
  # -- 桌面入口文件 -----------------------------------------------------------
288
306
 
289
307
 
308
+ def _desktop_session_present() -> bool:
309
+ return bool(
310
+ os.environ.get("DISPLAY")
311
+ or os.environ.get("WAYLAND_DISPLAY")
312
+ or os.environ.get("XDG_CURRENT_DESKTOP")
313
+ )
314
+
315
+
316
+ def ensure_desktop_entry() -> None:
317
+ """首次运行 tray 时自动注册应用菜单入口,并在路径变化后自愈刷新。
318
+
319
+ pip 的 wheel 安装没有装后钩子,因此采用运行时自注册:仅在有桌面
320
+ 会话的环境执行,内容与当前渲染一致时跳过写入。开机自启已开启时
321
+ 同步刷新其 Exec 路径,避免包位置变化后自启失效。
322
+ """
323
+ if not _desktop_session_present():
324
+ return
325
+ executable = shutil.which("agent-trail") or sys.argv[0]
326
+ if not _looks_executable(executable):
327
+ return
328
+ entry = render_desktop_entry(executable, ICON_PATH)
329
+ try:
330
+ if not APP_MENU_PATH.exists() or APP_MENU_PATH.read_text(encoding="utf-8") != entry:
331
+ APP_MENU_PATH.parent.mkdir(parents=True, exist_ok=True)
332
+ APP_MENU_PATH.write_text(entry, encoding="utf-8")
333
+ if autostart_enabled():
334
+ if AUTOSTART_PATH.read_text(encoding="utf-8") != entry:
335
+ AUTOSTART_PATH.write_text(entry, encoding="utf-8")
336
+ except OSError:
337
+ pass # 桌面注册失败不影响托盘本身;--install/--uninstall 仍可手动操作
338
+
339
+
290
340
  def render_desktop_entry(executable: str, icon: Path,
291
341
  template: Path = DESKTOP_TEMPLATE) -> str:
292
342
  text = template.read_text(encoding="utf-8")
@@ -327,6 +377,13 @@ class SingleInstance:
327
377
  os.write(self._fd, f"{os.getpid()}\n".encode())
328
378
  return True
329
379
 
380
+ def rewrite_pid(self) -> None:
381
+ """守护化后由最终进程调用:锁文件里写自己的 pid。"""
382
+ if self._fd is not None:
383
+ os.ftruncate(self._fd, 0)
384
+ os.lseek(self._fd, 0, os.SEEK_SET) # ftruncate 不回卷写偏移,必须显式归零
385
+ os.write(self._fd, f"{os.getpid()}\n".encode())
386
+
330
387
  def release(self) -> None:
331
388
  if self._fd is not None:
332
389
  try:
@@ -497,6 +554,28 @@ def run_gtk(icon_path: Path, show: bool = False) -> int:
497
554
  # -- 入口 --------------------------------------------------------------------
498
555
 
499
556
 
557
+ def _daemonize(log_path: Path) -> bool:
558
+ """双 fork 脱离终端;返回 False 表示当前在父进程(调用方应立即退出)。
559
+
560
+ 守护进程的 stdout/stderr 追加到日志文件,stdin 接到 /dev/null。
561
+ 单实例锁的 fd 随 fork 继承,由守护进程继续持有。
562
+ """
563
+ if os.fork() > 0:
564
+ return False
565
+ os.setsid()
566
+ if os.fork() > 0:
567
+ os._exit(0)
568
+ log_fd = os.open(log_path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
569
+ null_fd = os.open(os.devnull, os.O_RDONLY)
570
+ os.dup2(null_fd, 0)
571
+ os.dup2(log_fd, 1)
572
+ os.dup2(log_fd, 2)
573
+ for fd in (log_fd, null_fd):
574
+ if fd > 2:
575
+ os.close(fd)
576
+ return True
577
+
578
+
500
579
  def _reexec_with_system_python(argv: list[str], guard: "SingleInstance | None" = None) -> int | None:
501
580
  """当前解释器缺 GI 绑定时,改用带 gi 的系统 Python 重执行自己。
502
581
 
@@ -540,6 +619,7 @@ def main(argv: list[str] | None = None) -> int:
540
619
  parser.add_argument("--install", action="store_true", help="写入应用菜单入口")
541
620
  parser.add_argument("--uninstall", action="store_true", help="移除应用菜单入口与开机自启")
542
621
  parser.add_argument("--show", action="store_true", help="启动时同时打开控制台窗口")
622
+ parser.add_argument("--foreground", action="store_true", help="前台运行(默认脱离终端后台运行,日志见 ~/.agent-trail/tray.log)")
543
623
  args = parser.parse_args(argv)
544
624
 
545
625
  if args.uninstall:
@@ -558,10 +638,15 @@ def main(argv: list[str] | None = None) -> int:
558
638
  if not ICON_PATH.is_file():
559
639
  print(tr("error.no_icon", path=ICON_PATH), file=sys.stderr)
560
640
  return 1
641
+ ensure_desktop_entry()
642
+ # 锁在前台获取:重复启动时提示直接出现在用户终端;fd 由守护进程继承
561
643
  guard = SingleInstance()
562
644
  if not guard.acquire():
563
645
  print(tr("error.already_running"), flush=True)
564
646
  return 0
647
+ if not args.foreground and not _daemonize(LOG_PATH):
648
+ return 0 # 父进程:立即把终端还给用户;托盘在后台继续
649
+ guard.rewrite_pid() # 锁文件写守护进程的 pid,kill $(cat 锁文件) 才有效
565
650
  try:
566
651
  return run_gtk(ICON_PATH, show=args.show)
567
652
  except (ImportError, ValueError) as error:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: session-trail
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Local cross-agent work timeline: discover, browse and resume coding-agent sessions on your machine
5
5
  Author: fwt11
6
6
  License-Expression: MIT
@@ -101,9 +101,11 @@ agent-trail serve --interval 30 # 轮询间隔(watch 健康时自动降
101
101
  ```bash
102
102
  agent-trail tray # 启动托盘(自动拉起 agent-trail serve)
103
103
  agent-trail tray --show # 启动同时打开控制台窗口
104
- agent-trail tray --install # 写入应用菜单入口;--uninstall 反向清理
104
+ agent-trail tray --install # 手动写入应用菜单入口;--uninstall 反向清理
105
105
  ```
106
106
 
107
+ pip 安装后首次运行 `agent-trail tray` 会自动把"Agent 足迹"注册进系统应用菜单(应用启动器可直接搜索到),之后升级换路径也会自动刷新;无桌面会话(SSH/服务器)不写入。
108
+
107
109
  托盘壳本身不依赖任何第三方 pip 包,但需要 GTK 的 GI 绑定(系统 Python 通常自带)。`agent-trail tray` 在当前解释器缺 GI 绑定时会自动改用带绑定的系统 Python 重执行(例如 pip 装在 venv 里、托盘跑在系统 Python),无需任何手动操作;实在找不到时按提示安装:
108
110
 
109
111
  ```bash
@@ -111,7 +113,7 @@ agent-trail tray --install # 写入应用菜单入口;--uninstall 反向清
111
113
  sudo apt install python3-gi gir1.2-webkit2-4.1
112
114
  ```
113
115
 
114
- 托盘以子进程方式管理 `agent-trail serve`(可用环境变量 `AGENT_TRAIL_BIN` 覆盖可执行文件路径),服务异常退出自动重启(退避重试,连续失败后停在可手动启动的状态)。窗口直接关闭只是隐藏,退出走托盘菜单;flock 单实例锁防止重复启动。
116
+ `agent-trail tray` 默认脱离终端后台运行:命令立即返回,日志追加在 `~/.agent-trail/tray.log`,关闭终端不影响托盘;`--foreground` 可前台运行调试。托盘以子进程方式管理 `agent-trail serve`——**启动托盘即自动拉起服务**(菜单里可停止/启动,异常退出自动重启)。窗口直接关闭只是隐藏,退出走托盘菜单,或 `kill $(cat ~/.agent-trail/tray.lock)`;flock 单实例锁防止重复启动(重复执行会提示"已在运行")。
115
117
 
116
118
  托盘图标走原生 StatusNotifierItem/DBusMenu 协议(`agent_trail/tray/sni.py`,Gio 实现——libayatana 在 Deepin 23 的托盘守护上不工作,详见源码注释)。内嵌 WebView 默认使用软件合成(`WEBKIT_DISABLE_DMABUF_RENDERER` / `WEBKIT_DISABLE_COMPOSITING_MODE`,可用同名环境变量覆盖),避免在无 DRM 权限的会话里出现空白窗口。`serve` 的所有命令行形态保持不变,无托盘环境(SSH/无桌面)不受影响。
117
119
 
File without changes
File without changes
File without changes