session-trail 0.2.0__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.
- session_trail-0.2.0/LICENSE +21 -0
- session_trail-0.2.0/MANIFEST.in +5 -0
- session_trail-0.2.0/PKG-INFO +155 -0
- session_trail-0.2.0/README.en.md +128 -0
- session_trail-0.2.0/README.md +129 -0
- session_trail-0.2.0/pyproject.toml +42 -0
- session_trail-0.2.0/setup.cfg +4 -0
- session_trail-0.2.0/src/agent_trail/__init__.py +1 -0
- session_trail-0.2.0/src/agent_trail/__main__.py +3 -0
- session_trail-0.2.0/src/agent_trail/adapters/__init__.py +26 -0
- session_trail-0.2.0/src/agent_trail/adapters/base.py +39 -0
- session_trail-0.2.0/src/agent_trail/adapters/claude_code.py +40 -0
- session_trail-0.2.0/src/agent_trail/adapters/codebuddy.py +93 -0
- session_trail-0.2.0/src/agent_trail/adapters/codex.py +165 -0
- session_trail-0.2.0/src/agent_trail/adapters/deepseek_harness.py +179 -0
- session_trail-0.2.0/src/agent_trail/adapters/jsonl_utils.py +53 -0
- session_trail-0.2.0/src/agent_trail/adapters/kimi_code.py +112 -0
- session_trail-0.2.0/src/agent_trail/adapters/minimax_code.py +152 -0
- session_trail-0.2.0/src/agent_trail/adapters/opencode.py +169 -0
- session_trail-0.2.0/src/agent_trail/adapters/pi.py +114 -0
- session_trail-0.2.0/src/agent_trail/adapters/sqlite_utils.py +15 -0
- session_trail-0.2.0/src/agent_trail/adapters/zcode.py +161 -0
- session_trail-0.2.0/src/agent_trail/cli.py +134 -0
- session_trail-0.2.0/src/agent_trail/launcher.py +114 -0
- session_trail-0.2.0/src/agent_trail/models.py +86 -0
- session_trail-0.2.0/src/agent_trail/scanner.py +103 -0
- session_trail-0.2.0/src/agent_trail/server.py +260 -0
- session_trail-0.2.0/src/agent_trail/static/app.js +525 -0
- session_trail-0.2.0/src/agent_trail/static/i18n.js +248 -0
- session_trail-0.2.0/src/agent_trail/static/index.html +134 -0
- session_trail-0.2.0/src/agent_trail/static/styles.css +307 -0
- session_trail-0.2.0/src/agent_trail/store.py +197 -0
- session_trail-0.2.0/src/agent_trail/tray/__init__.py +6 -0
- session_trail-0.2.0/src/agent_trail/tray/app.py +582 -0
- session_trail-0.2.0/src/agent_trail/tray/desktop.template +10 -0
- session_trail-0.2.0/src/agent_trail/tray/icon.png +0 -0
- session_trail-0.2.0/src/agent_trail/tray/sni.py +384 -0
- session_trail-0.2.0/src/agent_trail/watcher.py +307 -0
- session_trail-0.2.0/src/session_trail.egg-info/PKG-INFO +155 -0
- session_trail-0.2.0/src/session_trail.egg-info/SOURCES.txt +41 -0
- session_trail-0.2.0/src/session_trail.egg-info/dependency_links.txt +1 -0
- session_trail-0.2.0/src/session_trail.egg-info/entry_points.txt +2 -0
- session_trail-0.2.0/src/session_trail.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fwt11
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: session-trail
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Local cross-agent work timeline: discover, browse and resume coding-agent sessions on your machine
|
|
5
|
+
Author: fwt11
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fwt11/agent_trail
|
|
8
|
+
Project-URL: Repository, https://github.com/fwt11/agent_trail
|
|
9
|
+
Project-URL: Issues, https://github.com/fwt11/agent_trail/issues
|
|
10
|
+
Keywords: ai-agents,coding-agent,timeline,session-management,local-first,tray,systray
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Environment :: X11 Applications
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Agent 足迹(agent-trail)
|
|
28
|
+
|
|
29
|
+
中文 | [English](README.en.md)
|
|
30
|
+
|
|
31
|
+
跨 Agent 的本地工作记忆:自动发现你机器上各种编码 Agent 留下的会话记录,汇成一条按日期分组的时间线,帮你回答"昨天那个东西我到底是用哪个工具、在哪个目录做的",并尽量一键回到原会话继续工作。
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
如果你在多个编码 Agent(Kimi Code、CodeBuddy、ZCode、OpenCode……)之间来回切换,大概也经历过这种时刻:只记得"昨天做过某个东西",却想不起用了哪个工具、在哪个目录。这个工具做的就是缩短"模糊回忆 → 找到工作现场 → 继续工作"的路径。
|
|
36
|
+
|
|
37
|
+
## 特性
|
|
38
|
+
|
|
39
|
+
- **自动发现**:只读扫描各 Agent 已保存的本机会话(JSONL / JSON / SQLite),不改变任何现有使用习惯
|
|
40
|
+
- **统一时间线**:按本地日期分组、按最后活动时间倒序;支持今天/昨天/最近 7 天、Agent、项目目录和标题/目录文本筛选
|
|
41
|
+
- **实时更新**:Linux 下用 inotify 监控源文件变化,秒级反映新会话(不可用时自动回退到定时轮询)
|
|
42
|
+
- **工作段折叠**:同一 Agent、同一目录、相邻间隔 ≤45 分钟的会话可折叠为一段,展开仍能定位每个原会话
|
|
43
|
+
- **回到原会话**:对真实验证过恢复路径的工具,在原工作目录起一个新终端执行对应恢复命令
|
|
44
|
+
- **隐私优先**:本地索引只存元数据(标题、目录、时间、统计),不存任何消息正文、代码、diff 或凭据;HTTP 服务只绑定 127.0.0.1,每次启动生成高熵访问令牌
|
|
45
|
+
- **诚实降级**:没有原生标题就显示"未命名会话",恢复路径没验证过就不显示恢复按钮,绝不伪造
|
|
46
|
+
|
|
47
|
+
## 支持的 Agent
|
|
48
|
+
|
|
49
|
+
| Agent | 时间线 | 回到原会话 | 说明 |
|
|
50
|
+
| --- | :-: | :-: | --- |
|
|
51
|
+
| Kimi Code | ✅ | ✅ | `kimi --session <id>`,真机验证 |
|
|
52
|
+
| CodeBuddy | ✅ | ✅ | `codebuddy --resume <id>`,真机验证 |
|
|
53
|
+
| MiniMax Code | ✅ | ✅ | `mcode --session <id>`,主会话真机验证;子代理会话只读 |
|
|
54
|
+
| OpenCode | ✅ | ✅ | `opencode --session <id>`,真机验证 |
|
|
55
|
+
| ZCode | ✅ | ➖ | 深链路由未验证,只读展示 |
|
|
56
|
+
| DeepSeek Harness | ✅ | ➖ | 恢复未通过真机验证,只读展示 |
|
|
57
|
+
| Pi | ✅ | ➖ | 恢复未通过真机验证,只读展示 |
|
|
58
|
+
| Codex | ✅ | ➖ | 恢复需要目录信任决策,未授权验证,只读展示 |
|
|
59
|
+
| Claude Code | ⚠️ | ➖ | 实验性检测,本机无数据时不产生虚假记录 |
|
|
60
|
+
|
|
61
|
+
## 安装
|
|
62
|
+
|
|
63
|
+
要求:Python ≥ 3.10,Linux(inotify 实时监控为 Linux 特性,其余平台自动退化为轮询)。**零第三方运行时依赖**,纯标准库。
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install session-trail
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
或从源码安装:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/fwt11/agent_trail.git
|
|
73
|
+
cd agent_trail
|
|
74
|
+
pip install .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 使用
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
agent-trail scan # 扫描一次,输出各来源状态与记录数
|
|
81
|
+
agent-trail list # 以 JSON Lines 输出全部会话元数据
|
|
82
|
+
agent-trail serve # 启动本地控制台(默认自动打开浏览器)
|
|
83
|
+
agent-trail tray # 系统托盘形态(Linux,见下)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`serve` 常用选项:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
agent-trail serve --port 8731 # 固定端口(默认随机)
|
|
90
|
+
agent-trail serve --no-browser # 不自动打开浏览器
|
|
91
|
+
agent-trail serve --no-watch # 禁用 inotify,只用定时轮询
|
|
92
|
+
agent-trail serve --interval 30 # 轮询间隔(watch 健康时自动降为 5 分钟安全网)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
启动后控制台 URL 带一次性高熵访问令牌(如 `http://127.0.0.1:8731/?key=…`);令牌保存在浏览器 sessionStorage,后续请求通过请求头携带,所有 API 都会校验。手动刷新按钮立即触发一次扫描。
|
|
96
|
+
|
|
97
|
+
## 桌面托盘(Linux)
|
|
98
|
+
|
|
99
|
+
`serve` 之外还有系统托盘形态,已整合为子命令:内嵌窗口展示控制台,托盘菜单提供打开控制台、立即刷新、启动/停止服务、开机自启和退出。
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
agent-trail tray # 启动托盘(自动拉起 agent-trail serve)
|
|
103
|
+
agent-trail tray --show # 启动同时打开控制台窗口
|
|
104
|
+
agent-trail tray --install # 写入应用菜单入口;--uninstall 反向清理
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
托盘壳本身不依赖任何第三方 pip 包,但需要 GTK 的 GI 绑定(系统 Python 通常自带)。`agent-trail tray` 在当前解释器缺 GI 绑定时会自动改用带绑定的系统 Python 重执行(例如 pip 装在 venv 里、托盘跑在系统 Python),无需任何手动操作;实在找不到时按提示安装:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Debian/Ubuntu/deepin 系
|
|
111
|
+
sudo apt install python3-gi gir1.2-webkit2-4.1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
托盘以子进程方式管理 `agent-trail serve`(可用环境变量 `AGENT_TRAIL_BIN` 覆盖可执行文件路径),服务异常退出自动重启(退避重试,连续失败后停在可手动启动的状态)。窗口直接关闭只是隐藏,退出走托盘菜单;flock 单实例锁防止重复启动。
|
|
115
|
+
|
|
116
|
+
托盘图标走原生 StatusNotifierItem/DBusMenu 协议(`agent_trail/tray/sni.py`,Gio 实现——libayatana 在 Deepin 23 的托盘守护上不工作,详见源码注释)。内嵌 WebView 默认使用软件合成(`WEBKIT_DISABLE_DMABUF_RENDERER` / `WEBKIT_DISABLE_COMPOSITING_MODE`,可用同名环境变量覆盖),避免在无 DRM 权限的会话里出现空白窗口。`serve` 的所有命令行形态保持不变,无托盘环境(SSH/无桌面)不受影响。
|
|
117
|
+
|
|
118
|
+
## 工作原理
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
各 Agent 本地数据 本工具 呈现
|
|
122
|
+
───────────────── ───────────────────────── ─────────────────
|
|
123
|
+
JSONL / JSON / SQLite ─▶ 适配器(9 种,只读) ─▶ 元数据索引 ─▶ 本地网页时间线
|
|
124
|
+
~/.kimi-code/… 规范化为统一模型 (SQLite) 127.0.0.1 + 令牌
|
|
125
|
+
~/.zcode/… 指纹增量去重 筛选 / 详情 / 恢复
|
|
126
|
+
~/.codex/… inotify 事件触发重扫
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- **适配器契约**:每个 Agent 一个适配器,实现发现(discover)、扫描(scan)、状态(status)、恢复计划(resume_plan)四个边界;数据源格式变化时报告不兼容而不是猜测
|
|
130
|
+
- **增量扫描**:以源路径、大小、mtime 和适配器版本计算指纹,未变化的来源毫秒级跳过
|
|
131
|
+
- **实时监控**:`watcher.py` 用 ctypes 直接调 libc inotify(保持零依赖),目录级 watch 覆盖原子替换与 WAL sidecar 变化;去抖 + 最小间隔合并高频写入
|
|
132
|
+
- **恢复安全**:恢复命令来自适配器白名单参数数组,不经过 shell;API 只接受会话标识,服务端重新查库生成计划,调用者无法注入命令或目录
|
|
133
|
+
- **源数据只读**:SQLite 源以 `mode=ro` 打开,禁止任何写事务;源记录删除后保留最后元数据并标记"源已缺失"、禁用恢复
|
|
134
|
+
|
|
135
|
+
## 隐私与安全
|
|
136
|
+
|
|
137
|
+
- 明确不读取各工具的 credentials、auth、token、密钥类文件
|
|
138
|
+
- 本地索引与日志不保存消息正文、代码、命令、diff、附件或凭据
|
|
139
|
+
- HTTP 仅绑定回环地址;刷新与恢复使用 POST 并校验 Origin,防止外部网页诱导本机服务执行动作
|
|
140
|
+
- 网页不加载任何第三方脚本、字体或统计
|
|
141
|
+
|
|
142
|
+
## 已知限制
|
|
143
|
+
|
|
144
|
+
- 元数据优先:部分会话只有"未命名会话",以目录和时间辅助识别;不读取首条用户消息补标题
|
|
145
|
+
- 适配器基于当前各工具的本机数据格式,工具升级后可能需要更新
|
|
146
|
+
- "回到工作现场"指原 Agent 会话与目录,不保证恢复未保存的文件或桌面布局
|
|
147
|
+
|
|
148
|
+
## 开发文档
|
|
149
|
+
|
|
150
|
+
- [设计文档](docs/design.md)
|
|
151
|
+
- [实施计划](docs/implementation-plan.md)
|
|
152
|
+
|
|
153
|
+
## 许可证
|
|
154
|
+
|
|
155
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Agent Trail (Agent 足迹)
|
|
2
|
+
|
|
3
|
+
[中文](README.md) | English
|
|
4
|
+
|
|
5
|
+
A local-first work memory for people who use multiple coding agents: it discovers the sessions your agents already saved on this machine, lines them up on one dated timeline, and — where verified — drops you back into the original session with one click. Answers the "I did something yesterday… with which tool, in which directory?" problem.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Automatic discovery**: read-only scanning of the sessions each agent already stores locally (JSONL / JSON / SQLite); nothing about your existing habits changes
|
|
12
|
+
- **One timeline**: grouped by local date, newest first; filter by today/yesterday/last 7 days, agent, project directory, and title/directory text
|
|
13
|
+
- **Live updates**: on Linux, inotify watches the source files and reflects new sessions within seconds (falls back to interval polling automatically)
|
|
14
|
+
- **Work segments**: sessions from the same tool, same directory, within 45 minutes fold into one segment — expandable down to every original session
|
|
15
|
+
- **Resume original session**: for tools whose resume path is verified, opens a new terminal in the original working directory
|
|
16
|
+
- **Privacy first**: the local index stores metadata only (titles, directories, timestamps, stats) — never message bodies, code, diffs or credentials; the HTTP console binds to 127.0.0.1 with a per-start high-entropy access token
|
|
17
|
+
- **Honest degradation**: no native title → "Unnamed session"; unverified resume paths → no resume button. It never fakes capabilities
|
|
18
|
+
- **i18n**: the console UI and tray menu ship in Simplified Chinese and English; the console follows your browser language (`?lang=en` / `?lang=zh-CN` to override), the tray follows `LANG` (`AGENT_TRAIL_LANG` overrides)
|
|
19
|
+
|
|
20
|
+
## Supported agents
|
|
21
|
+
|
|
22
|
+
| Agent | Timeline | Resume | Notes |
|
|
23
|
+
| --- | :-: | :-: | --- |
|
|
24
|
+
| Kimi Code | ✅ | ✅ | `kimi --session <id>`, verified on a real machine |
|
|
25
|
+
| CodeBuddy | ✅ | ✅ | `codebuddy --resume <id>`, verified |
|
|
26
|
+
| MiniMax Code | ✅ | ✅ | `mcode --session <id>`, verified for main sessions; subagent sessions are view-only |
|
|
27
|
+
| OpenCode | ✅ | ✅ | `opencode --session <id>`, verified |
|
|
28
|
+
| ZCode | ✅ | ➖ | deep-link route unverified; view-only |
|
|
29
|
+
| DeepSeek Harness | ✅ | ➖ | resume failed verification; view-only |
|
|
30
|
+
| Pi | ✅ | ➖ | resume failed verification; view-only |
|
|
31
|
+
| Codex | ✅ | ➖ | resume needs an interactive directory-trust decision; view-only |
|
|
32
|
+
| Claude Code | ⚠️ | ➖ | experimental detection; no fake records without real data |
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
Requires Python ≥ 3.10 and Linux for the inotify live updates (other platforms degrade to polling). **Zero third-party runtime dependencies** — standard library only.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install session-trail
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or from source:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/fwt11/agent_trail.git
|
|
46
|
+
cd agent_trail
|
|
47
|
+
pip install .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
agent-trail scan # scan once, print per-source status and record counts
|
|
54
|
+
agent-trail list # print all session metadata as JSON Lines
|
|
55
|
+
agent-trail serve # start the local console (opens the browser by default)
|
|
56
|
+
agent-trail tray # system tray app (Linux, see below)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Common `serve` options:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
agent-trail serve --port 8731 # fixed port (random by default)
|
|
63
|
+
agent-trail serve --no-browser # do not open the browser
|
|
64
|
+
agent-trail serve --no-watch # disable inotify, poll on an interval
|
|
65
|
+
agent-trail serve --interval 30 # poll interval (drops to a 5-minute safety net while watching)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The console URL carries a one-time high-entropy token (`http://127.0.0.1:8731/?key=…`); the token lives in the browser's sessionStorage and every API call is checked. The refresh button triggers a scan immediately.
|
|
69
|
+
|
|
70
|
+
## Desktop tray (Linux)
|
|
71
|
+
|
|
72
|
+
Beyond `serve`, a tray application embeds the console in a native window and adds a tray menu: open console, refresh, start/stop service, launch at login, quit.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
agent-trail tray # start the tray (spawns agent-trail serve)
|
|
76
|
+
agent-trail tray --show # also open the console window
|
|
77
|
+
agent-trail tray --install # add an applications-menu entry; --uninstall reverts
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
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
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Debian/Ubuntu/deepin
|
|
84
|
+
sudo apt install python3-gi gir1.2-webkit2-4.1
|
|
85
|
+
```
|
|
86
|
+
|
|
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.
|
|
88
|
+
|
|
89
|
+
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
|
+
|
|
91
|
+
## How it works
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Agent session data this tool presentation
|
|
95
|
+
────────────────── ───────────────────────── ─────────────────
|
|
96
|
+
JSONL / JSON / SQLite ─▶ 9 read-only adapters ─▶ metadata index ─▶ local web console
|
|
97
|
+
~/.kimi-code/… normalized model (SQLite) 127.0.0.1 + token
|
|
98
|
+
~/.zcode/… fingerprinted deltas filters / details / resume
|
|
99
|
+
~/.codex/… inotify-triggered rescans
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **Adapter contract**: one adapter per agent implementing discover / scan / status / resume_plan; incompatible formats are reported, never guessed
|
|
103
|
+
- **Incremental scans**: a fingerprint over source paths, sizes, mtimes and adapter version; unchanged sources skip in milliseconds
|
|
104
|
+
- **Live watching**: `watcher.py` calls libc inotify via ctypes (staying dependency-free); directory-level watches cover atomic renames and WAL sidecars; debounce + minimum spacing merge write bursts
|
|
105
|
+
- **Resume safety**: resume commands come from an adapter whitelist and run as argument arrays — never through a shell; the API only accepts session identifiers and regenerates the plan server-side
|
|
106
|
+
- **Source data is read-only**: SQLite sources open with `mode=ro`; deleted source records keep their last metadata, flagged missing with resume disabled
|
|
107
|
+
|
|
108
|
+
## Privacy & security
|
|
109
|
+
|
|
110
|
+
- Never reads credentials, auth, token, or key files of any agent
|
|
111
|
+
- The local index and logs contain no message bodies, code, commands, diffs, attachments or credentials
|
|
112
|
+
- HTTP binds to the loopback interface only; refresh and resume are POST with Origin checks, so foreign web pages cannot drive the local service
|
|
113
|
+
- The web console loads no third-party scripts, fonts or analytics
|
|
114
|
+
|
|
115
|
+
## Known limitations
|
|
116
|
+
|
|
117
|
+
- Metadata-first: some sessions appear as "Unnamed session", identified by directory and time; first user messages are never read to fabricate titles
|
|
118
|
+
- Adapters target current on-disk formats; tool upgrades may require adapter updates
|
|
119
|
+
- "Back to the work site" means the original agent session and directory — unsaved files or desktop layout are not restored
|
|
120
|
+
|
|
121
|
+
## Development docs
|
|
122
|
+
|
|
123
|
+
- [Design document](docs/design.md) (Chinese)
|
|
124
|
+
- [Implementation plan](docs/implementation-plan.md) (Chinese)
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Agent 足迹(agent-trail)
|
|
2
|
+
|
|
3
|
+
中文 | [English](README.en.md)
|
|
4
|
+
|
|
5
|
+
跨 Agent 的本地工作记忆:自动发现你机器上各种编码 Agent 留下的会话记录,汇成一条按日期分组的时间线,帮你回答"昨天那个东西我到底是用哪个工具、在哪个目录做的",并尽量一键回到原会话继续工作。
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
如果你在多个编码 Agent(Kimi Code、CodeBuddy、ZCode、OpenCode……)之间来回切换,大概也经历过这种时刻:只记得"昨天做过某个东西",却想不起用了哪个工具、在哪个目录。这个工具做的就是缩短"模糊回忆 → 找到工作现场 → 继续工作"的路径。
|
|
10
|
+
|
|
11
|
+
## 特性
|
|
12
|
+
|
|
13
|
+
- **自动发现**:只读扫描各 Agent 已保存的本机会话(JSONL / JSON / SQLite),不改变任何现有使用习惯
|
|
14
|
+
- **统一时间线**:按本地日期分组、按最后活动时间倒序;支持今天/昨天/最近 7 天、Agent、项目目录和标题/目录文本筛选
|
|
15
|
+
- **实时更新**:Linux 下用 inotify 监控源文件变化,秒级反映新会话(不可用时自动回退到定时轮询)
|
|
16
|
+
- **工作段折叠**:同一 Agent、同一目录、相邻间隔 ≤45 分钟的会话可折叠为一段,展开仍能定位每个原会话
|
|
17
|
+
- **回到原会话**:对真实验证过恢复路径的工具,在原工作目录起一个新终端执行对应恢复命令
|
|
18
|
+
- **隐私优先**:本地索引只存元数据(标题、目录、时间、统计),不存任何消息正文、代码、diff 或凭据;HTTP 服务只绑定 127.0.0.1,每次启动生成高熵访问令牌
|
|
19
|
+
- **诚实降级**:没有原生标题就显示"未命名会话",恢复路径没验证过就不显示恢复按钮,绝不伪造
|
|
20
|
+
|
|
21
|
+
## 支持的 Agent
|
|
22
|
+
|
|
23
|
+
| Agent | 时间线 | 回到原会话 | 说明 |
|
|
24
|
+
| --- | :-: | :-: | --- |
|
|
25
|
+
| Kimi Code | ✅ | ✅ | `kimi --session <id>`,真机验证 |
|
|
26
|
+
| CodeBuddy | ✅ | ✅ | `codebuddy --resume <id>`,真机验证 |
|
|
27
|
+
| MiniMax Code | ✅ | ✅ | `mcode --session <id>`,主会话真机验证;子代理会话只读 |
|
|
28
|
+
| OpenCode | ✅ | ✅ | `opencode --session <id>`,真机验证 |
|
|
29
|
+
| ZCode | ✅ | ➖ | 深链路由未验证,只读展示 |
|
|
30
|
+
| DeepSeek Harness | ✅ | ➖ | 恢复未通过真机验证,只读展示 |
|
|
31
|
+
| Pi | ✅ | ➖ | 恢复未通过真机验证,只读展示 |
|
|
32
|
+
| Codex | ✅ | ➖ | 恢复需要目录信任决策,未授权验证,只读展示 |
|
|
33
|
+
| Claude Code | ⚠️ | ➖ | 实验性检测,本机无数据时不产生虚假记录 |
|
|
34
|
+
|
|
35
|
+
## 安装
|
|
36
|
+
|
|
37
|
+
要求:Python ≥ 3.10,Linux(inotify 实时监控为 Linux 特性,其余平台自动退化为轮询)。**零第三方运行时依赖**,纯标准库。
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install session-trail
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
或从源码安装:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/fwt11/agent_trail.git
|
|
47
|
+
cd agent_trail
|
|
48
|
+
pip install .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 使用
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
agent-trail scan # 扫描一次,输出各来源状态与记录数
|
|
55
|
+
agent-trail list # 以 JSON Lines 输出全部会话元数据
|
|
56
|
+
agent-trail serve # 启动本地控制台(默认自动打开浏览器)
|
|
57
|
+
agent-trail tray # 系统托盘形态(Linux,见下)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`serve` 常用选项:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
agent-trail serve --port 8731 # 固定端口(默认随机)
|
|
64
|
+
agent-trail serve --no-browser # 不自动打开浏览器
|
|
65
|
+
agent-trail serve --no-watch # 禁用 inotify,只用定时轮询
|
|
66
|
+
agent-trail serve --interval 30 # 轮询间隔(watch 健康时自动降为 5 分钟安全网)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
启动后控制台 URL 带一次性高熵访问令牌(如 `http://127.0.0.1:8731/?key=…`);令牌保存在浏览器 sessionStorage,后续请求通过请求头携带,所有 API 都会校验。手动刷新按钮立即触发一次扫描。
|
|
70
|
+
|
|
71
|
+
## 桌面托盘(Linux)
|
|
72
|
+
|
|
73
|
+
`serve` 之外还有系统托盘形态,已整合为子命令:内嵌窗口展示控制台,托盘菜单提供打开控制台、立即刷新、启动/停止服务、开机自启和退出。
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
agent-trail tray # 启动托盘(自动拉起 agent-trail serve)
|
|
77
|
+
agent-trail tray --show # 启动同时打开控制台窗口
|
|
78
|
+
agent-trail tray --install # 写入应用菜单入口;--uninstall 反向清理
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
托盘壳本身不依赖任何第三方 pip 包,但需要 GTK 的 GI 绑定(系统 Python 通常自带)。`agent-trail tray` 在当前解释器缺 GI 绑定时会自动改用带绑定的系统 Python 重执行(例如 pip 装在 venv 里、托盘跑在系统 Python),无需任何手动操作;实在找不到时按提示安装:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Debian/Ubuntu/deepin 系
|
|
85
|
+
sudo apt install python3-gi gir1.2-webkit2-4.1
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
托盘以子进程方式管理 `agent-trail serve`(可用环境变量 `AGENT_TRAIL_BIN` 覆盖可执行文件路径),服务异常退出自动重启(退避重试,连续失败后停在可手动启动的状态)。窗口直接关闭只是隐藏,退出走托盘菜单;flock 单实例锁防止重复启动。
|
|
89
|
+
|
|
90
|
+
托盘图标走原生 StatusNotifierItem/DBusMenu 协议(`agent_trail/tray/sni.py`,Gio 实现——libayatana 在 Deepin 23 的托盘守护上不工作,详见源码注释)。内嵌 WebView 默认使用软件合成(`WEBKIT_DISABLE_DMABUF_RENDERER` / `WEBKIT_DISABLE_COMPOSITING_MODE`,可用同名环境变量覆盖),避免在无 DRM 权限的会话里出现空白窗口。`serve` 的所有命令行形态保持不变,无托盘环境(SSH/无桌面)不受影响。
|
|
91
|
+
|
|
92
|
+
## 工作原理
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
各 Agent 本地数据 本工具 呈现
|
|
96
|
+
───────────────── ───────────────────────── ─────────────────
|
|
97
|
+
JSONL / JSON / SQLite ─▶ 适配器(9 种,只读) ─▶ 元数据索引 ─▶ 本地网页时间线
|
|
98
|
+
~/.kimi-code/… 规范化为统一模型 (SQLite) 127.0.0.1 + 令牌
|
|
99
|
+
~/.zcode/… 指纹增量去重 筛选 / 详情 / 恢复
|
|
100
|
+
~/.codex/… inotify 事件触发重扫
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- **适配器契约**:每个 Agent 一个适配器,实现发现(discover)、扫描(scan)、状态(status)、恢复计划(resume_plan)四个边界;数据源格式变化时报告不兼容而不是猜测
|
|
104
|
+
- **增量扫描**:以源路径、大小、mtime 和适配器版本计算指纹,未变化的来源毫秒级跳过
|
|
105
|
+
- **实时监控**:`watcher.py` 用 ctypes 直接调 libc inotify(保持零依赖),目录级 watch 覆盖原子替换与 WAL sidecar 变化;去抖 + 最小间隔合并高频写入
|
|
106
|
+
- **恢复安全**:恢复命令来自适配器白名单参数数组,不经过 shell;API 只接受会话标识,服务端重新查库生成计划,调用者无法注入命令或目录
|
|
107
|
+
- **源数据只读**:SQLite 源以 `mode=ro` 打开,禁止任何写事务;源记录删除后保留最后元数据并标记"源已缺失"、禁用恢复
|
|
108
|
+
|
|
109
|
+
## 隐私与安全
|
|
110
|
+
|
|
111
|
+
- 明确不读取各工具的 credentials、auth、token、密钥类文件
|
|
112
|
+
- 本地索引与日志不保存消息正文、代码、命令、diff、附件或凭据
|
|
113
|
+
- HTTP 仅绑定回环地址;刷新与恢复使用 POST 并校验 Origin,防止外部网页诱导本机服务执行动作
|
|
114
|
+
- 网页不加载任何第三方脚本、字体或统计
|
|
115
|
+
|
|
116
|
+
## 已知限制
|
|
117
|
+
|
|
118
|
+
- 元数据优先:部分会话只有"未命名会话",以目录和时间辅助识别;不读取首条用户消息补标题
|
|
119
|
+
- 适配器基于当前各工具的本机数据格式,工具升级后可能需要更新
|
|
120
|
+
- "回到工作现场"指原 Agent 会话与目录,不保证恢复未保存的文件或桌面布局
|
|
121
|
+
|
|
122
|
+
## 开发文档
|
|
123
|
+
|
|
124
|
+
- [设计文档](docs/design.md)
|
|
125
|
+
- [实施计划](docs/implementation-plan.md)
|
|
126
|
+
|
|
127
|
+
## 许可证
|
|
128
|
+
|
|
129
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "session-trail"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Local cross-agent work timeline: discover, browse and resume coding-agent sessions on your machine"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "fwt11" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
dependencies = []
|
|
15
|
+
keywords = ["ai-agents", "coding-agent", "timeline", "session-management", "local-first", "tray", "systray"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Web Environment",
|
|
19
|
+
"Environment :: X11 Applications",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Topic :: Software Development",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/fwt11/agent_trail"
|
|
32
|
+
Repository = "https://github.com/fwt11/agent_trail"
|
|
33
|
+
Issues = "https://github.com/fwt11/agent_trail/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
agent-trail = "agent_trail.cli:main"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["src"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.package-data]
|
|
42
|
+
agent_trail = ["static/*", "tray/*.png", "tray/*.template"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .base import BaseAdapter
|
|
4
|
+
from .claude_code import ClaudeCodeAdapter
|
|
5
|
+
from .codebuddy import CodeBuddyAdapter
|
|
6
|
+
from .codex import CodexAdapter
|
|
7
|
+
from .deepseek_harness import DeepSeekHarnessAdapter
|
|
8
|
+
from .kimi_code import KimiCodeAdapter
|
|
9
|
+
from .minimax_code import MiniMaxCodeAdapter
|
|
10
|
+
from .opencode import OpenCodeAdapter
|
|
11
|
+
from .pi import PiAdapter
|
|
12
|
+
from .zcode import ZCodeAdapter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def default_adapters(home: Path) -> list[BaseAdapter]:
|
|
16
|
+
return [
|
|
17
|
+
KimiCodeAdapter(home),
|
|
18
|
+
CodeBuddyAdapter(home),
|
|
19
|
+
PiAdapter(home),
|
|
20
|
+
DeepSeekHarnessAdapter(home),
|
|
21
|
+
MiniMaxCodeAdapter(home),
|
|
22
|
+
ZCodeAdapter(home),
|
|
23
|
+
OpenCodeAdapter(home),
|
|
24
|
+
CodexAdapter(home),
|
|
25
|
+
ClaudeCodeAdapter(home),
|
|
26
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..models import ActivitySession, AdapterStatus, ResumePlan, ScanResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseAdapter(ABC):
|
|
11
|
+
source: str
|
|
12
|
+
adapter_version: int = 1
|
|
13
|
+
|
|
14
|
+
def __init__(self, home: Path) -> None:
|
|
15
|
+
self.home = home
|
|
16
|
+
self._status = AdapterStatus(source=self.source, kind="unavailable")
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def discover(self) -> AdapterStatus:
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
|
|
22
|
+
def status(self) -> AdapterStatus:
|
|
23
|
+
return self._status
|
|
24
|
+
|
|
25
|
+
def watch_roots(self) -> list[tuple[Path, bool]]:
|
|
26
|
+
"""Directories to watch for change notifications as (path, recursive).
|
|
27
|
+
|
|
28
|
+
Defaults to no watching; adapters that read files should return the
|
|
29
|
+
directories that contain their sources so misses degrade to polling.
|
|
30
|
+
"""
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def scan(self, previous_fingerprint: str | None, now: datetime) -> ScanResult:
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def resume_plan(self, session: ActivitySession) -> ResumePlan:
|
|
39
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ..models import ActivitySession, AdapterStatus, ResumePlan, ScanResult
|
|
7
|
+
from .base import BaseAdapter
|
|
8
|
+
from .jsonl_utils import fingerprint_paths
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ClaudeCodeAdapter(BaseAdapter):
|
|
12
|
+
source = "claude-code"
|
|
13
|
+
adapter_version = 1
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def root(self) -> Path:
|
|
17
|
+
return self.home / ".claude" / "projects"
|
|
18
|
+
|
|
19
|
+
def discover(self) -> AdapterStatus:
|
|
20
|
+
if not self.root.is_dir():
|
|
21
|
+
return AdapterStatus(source=self.source, kind="unavailable")
|
|
22
|
+
return AdapterStatus(
|
|
23
|
+
source=self.source,
|
|
24
|
+
kind="experimental",
|
|
25
|
+
message="session metadata format has not been verified",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def scan(self, previous_fingerprint: str | None, now: datetime) -> ScanResult:
|
|
29
|
+
discovery = self.discover()
|
|
30
|
+
if discovery.kind == "unavailable":
|
|
31
|
+
return ScanResult(discovery, None, [], True)
|
|
32
|
+
fingerprint = fingerprint_paths([], self.adapter_version)
|
|
33
|
+
return ScanResult(discovery, fingerprint, [], fingerprint != previous_fingerprint)
|
|
34
|
+
|
|
35
|
+
def resume_plan(self, session: ActivitySession) -> ResumePlan:
|
|
36
|
+
return ResumePlan(
|
|
37
|
+
kind="none",
|
|
38
|
+
workdir=session.workdir,
|
|
39
|
+
reason="Claude Code resume has not been verified",
|
|
40
|
+
)
|