@danceiny/gotry 0.0.1-rc.12 → 0.0.1-rc.13

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.
package/README.md CHANGED
@@ -2,171 +2,89 @@
2
2
 
3
3
  [![CI](https://github.com/Danceiny/gotry/actions/workflows/ci.yml/badge.svg)](https://github.com/Danceiny/gotry/actions/workflows/ci.yml)
4
4
 
5
- > **身体和灵魂,更多旅行,更少旅游。**
5
+ > **身体和灵魂,更多旅行,更少旅游。**
6
6
  > *Body and soul — more travel, less tourism.*
7
7
 
8
- **GoTry 是「从出发到下一次出发」的 AI 旅行 Agent**:你用一句话说想去哪、为什么想出发;它先问清楚你的工作时间和已订资源,再用**数学求解器**给你一份经过验证的行程方案。
8
+ **GoTry 是「从出发到下一次出发」的 AI 旅行 Agent**:你用一句话说想去哪、为什么想出发;它先问清楚你的工作时间和已订资源,再用**数学求解器**给你一份经过验证的行程方案。
9
9
 
10
10
  **GoTry is an AI travel agent for departure-to-next-departure.** Tell it where you want to go and why. It asks what's missing, then hands you a **formally verified itinerary** — not vibes.
11
11
 
12
- | | |
13
- |---|---|
14
- | **Version** | `v0.0.1-rc.11`(npm **latest 直指本版**,`npx @danceiny/gotry` 即得;[release notes](docs/release-notes.md)) |
15
- | **Status** | **M3 工程与分发面已就绪,但真实种子用户 evidence 未收口,M3 Exit 仍开放;M4 记忆域由 founder 授权并行推进,不构成 M3 Exit 证明;M5/M6 仅在各自 Entry gate 满足后启动。** M4 Issue #20 的 paired-cohort/active-planning/experience-reflux 证据合同与 synthetic fixture scorer 已接入 run-all §34(合成 N=3 不充当 Exit,真实 `observed_private` N≥5 paired cohort 待积累);18 工具(2026-08-29 +`gotry_session_login`);事务化状态账本(ADR-15,`gotry_async_terminal.v1`:4/4→`succeeded`/ledger `settled`/exit 0,非 4/4→`failed`/ledger `failed`/exit 2,终态复诵零重算)+ 双形态架构冻结(ADR-16:本地+Web 一套账本语义,tenant_id 一等字段);**会话数据面 #21**:字段 fixture scorer、双源合同与 waiting-attach no-spend 已进确定性回归,真实 sf-01..08 尚未验收;**2026-08-29 已知限制清算第一刀**:Z3 WASM race 根治(`z3-shared.ts` 单一实例+会话级互斥,run-all §1 重试止血退役+§30 并发回归闸)、薄壳遗留(`shell/`)物理删除;**2026-08-29 第二批:OTA 扁平化 + 账号授权闸 + 登录产品化**——飞猪 `search-hotel` 接入(酒店实时报价)、OTA 工具面去「主/降级」路由话术、会话检索授权闸(每会话一次 + 拒绝即会话内吊销)、`gotry_session_login` 登录产品化(登录在携程官网完成,gotry 只读票据名零值过手);全栈回归以 `scripts/run-all-tests.sh` 为准 |
16
- | **Repo** | [github.com/Danceiny/gotry](https://github.com/Danceiny/gotry) · CI: typecheck + 全栈回归(Node 22/24) |
17
- | **Runtime** | DeepSeek Harness **0.1.2-alpha.1**(vendored 源码 tarball `ts/dsh-runtime/vendor/`,上游 npm 未发版、从 GitHub tag `dsh-v0.1.2-alpha.1` 构建,溯源见 [`ts/dsh-runtime/vendor/README.md`](ts/dsh-runtime/vendor/README.md);[upstream](https://github.com/deepseek-ai/DeepSeek-Harness)) · Z3 (npm `z3-solver`) · LoopX (pipx, `~/.local/pipx/venvs/loopx/bin/loopx`) · Agent-Reach v1.5.0 (`.venv/bin/agent-reach`) |
18
- | **License** | **MIT** (2026-08-23 落定,见 [LICENSE](LICENSE)) |
19
-
20
-
21
12
  ---
22
13
 
23
- ## 它做什么 — What it does
24
-
25
- GoTry 把「想去哪」变成「能不能、怎么去」:
26
-
27
- | 阶段 | AI/Engine | 输什么 |
28
- |---|---|---|
29
- | **动机访谈** | LLM | 必问项:工作窗口 / 已订资源 / 出发城市 |
30
- | **事实抽取** | LLM | 工作窗口生效 + 休假语义识别 |
31
- | **可行性判定** | **Z3** | 哪些候选目的地**可行**、哪些**不可行**、为什么、**如何最小化改动让它变可行** |
32
- | **门到门全成本** | 求解器 | 真实飞行时长(含时差)+ 醒来起夜惩罚 + 接驳代价 + 到达精力 % |
33
- | **证据链** | 渲染层 | 每个数字标 `[骨架:openflights]`、`[实时API:hbcli]`、`[静态包:估算]` |
34
-
35
- **不像普通 AI 聊天**——LLM 只做理解和解释,**判定与算术是数学求解器**算的。
36
-
37
- > **Unlike a regular AI chat**, the LLM only translates and explains. **Decisions and arithmetic are computed by a Z3 solver**, not guessed.
38
-
39
- ---
40
-
41
- ## 🚀 快速开始 — Quick start
42
-
43
- ### 一行启动(npm,推荐)
14
+ ## 🚀 30 秒上手
44
15
 
45
16
  ```bash
46
- npx @danceiny/gotry web # 首跑会提示在当前目录建 .env(LLM_API_KEY=<DeepSeek key>)
17
+ npx @danceiny/gotry web
18
+ # 首跑会提示建 .env:LLM_API_KEY=<DeepSeek key 或 OpenAI 兼容 key>
19
+ # → 浏览器打开 http://127.0.0.1:3080,像聊天一样说「我想去大理三天」
47
20
  ```
48
21
 
49
- 装完即得 :3080 对话界面。已装 dsh 的用户同理——上面的命令会自动以 cordis patch
50
- 挂载 gotry 插件,无需任何额外配置(零成本启动)。
51
-
52
- > rc.5 为不可用首发(已停止指向);rc.9 装得上跑不起(缺依赖声明,被 rc.10 覆盖);`latest` 为 rc.11——`npx @danceiny/gotry` 即得。
53
-
54
- ### 源码安装(开发者)
55
-
56
- ```bash
57
- git clone https://github.com/Danceiny/gotry
58
- cd gotry
59
- ```
60
-
61
- #### A. 装 vendored dsh runtime(一次性,约 1 分钟)
62
-
63
- ```bash
64
- cd ts/dsh-runtime
65
- pnpm install # 装 vendored dsh 0.1.2-alpha.1 + 第三方依赖 (tarball 在 vendor/, 溯源见 vendor/README.md)
66
- cd ../..
67
- ```
68
-
69
- > 为什么这一步:dsh runtime 是 vendored 的(`ts/dsh-runtime/`)——
70
- > 避免 npm 一键分发时把整个 dsh 也打包进去。
71
- > 之后可以重复使用,无需重装。
72
-
73
- #### B. 环境前置 — Prerequisites
74
-
75
- - **Node 22+**(`nvm install 22` 或更高)
76
- - 一个 LLM API key(默认走 DeepSeek,也支持 OpenAI 兼容协议)
77
-
78
- #### C. 配 — Configure
79
-
80
- ```bash
81
- cp .env.example .env
82
- # 填 LLM_API_KEY(deepseek: sk-...; 也兼容 OpenAI/Anthropic 兼容协议)
83
- ```
84
-
85
- #### D. 跑 — Run(仓内入口,开发者)
86
-
87
- gotry 把 **DeepSeek Harness (dsh) 运行时**作为唯一推荐面——任何自然语言输入在那里面都能跟 GoTry 正常对话(自动调用引擎、追问缺失字段、给出证据链)。
22
+ | 你想要的 | 命令 |
23
+ |---|---|
24
+ | 🖥️ 对话规划(推荐) | `npx @danceiny/gotry web` → :3080 对话界面 |
25
+ | 🤖 脚本/一次性问答 | `npx @danceiny/gotry "我想从深圳休整两天,预算 3000"` |
26
+ | 🛠️ 开发者:仓内运行 | 见下方[源码安装](#-快速开始) |
88
27
 
89
- 启动方式有三种,对应三种使用场景:
28
+ - 前置:Node 22+;一个 LLM API key。零成本启动——dsh 运行时以 cordis patch 自动挂载,无额外配置。
29
+ - 例外提示:`:3080` 端口被占时先 `kill <PID>`;首启 6–15 秒属正常冷启动;异常退出会留证据到 `gotry-state/incidents.jsonl`(不静默)。
90
30
 
91
- | 入口 | 命令 | 什么时候用 | 给你什么 |
92
- |---|---|---|---|
93
- | **① dsh Web 对话框**(推荐) | `./gotry web` | 第一天上手;持续多轮规划;想看推理过程可视化 | `http://127.0.0.1:3080` 浏览器界面;带 GoTry 人格的对话框 |
94
- | **② headless 一次性问答** | `./gotry "一句完整任务..."` | 写脚本、CI 自动化、定向 LLM 调试 | stdout 输出 markdown 回答 |
95
- | **③ help** | `./gotry help` | 不知道用法 | 三行帮助 |
31
+ <details>
32
+ <summary>🛠️ 开发者:源码安装(仓内运行)</summary>
96
33
 
97
34
  ```bash
98
- # 推荐:打开对话框,正常打「我想去大理三天」,看引擎回答
99
- ./gotry web
100
- # → http://127.0.0.1:3080,在输入框里像跟 DeepSeek 对话一样打字
101
-
102
- # 调试/自动化:直接一问一答,无浏览器
103
- ./gotry "我想从深圳休整两天,预算 3000"
104
- # → stdout 拿到引擎判定 + 证据链 + 候选 gate
105
-
106
- # 不知道用法
107
- ./gotry help
35
+ git clone https://github.com/Danceiny/gotry && cd gotry
36
+ cd ts/dsh-runtime && pnpm install && cd ../.. # ① vendored dsh 0.1.2-alpha.1(一次性)
37
+ cp .env.example .env # 填 LLM_API_KEY
38
+ ./gotry web # ③ 仓内入口,同 npm 形态
108
39
  ```
109
40
 
110
- > ⚠️ **dsh 运行时端口冲突**:如果你之前已经手动 `node ...dsh web` 跑过,而后再 `./gotry web`,会因 `:3080` 占用启动失败。要么先 `kill <PID>`,要么直接用已经在跑的那个。
111
- >
112
- > **dsh 启动耗时**:第一次 cold start 6–15 秒(cordis 组合 + plugin 加载 + LLM 准备);之后热启动 < 2 秒。
113
- >
114
- > **headless 报错兜底**:意外退出(exit 0 / signal)会 fsync 一条记录到 `gotry-state/incidents.jsonl`,作为事故证据而不是沉默(D-NEW 进程护栏)。
41
+ | 入口 | 命令 | 什么时候用 |
42
+ |---|---|---|
43
+ | dsh Web 对话(推荐) | `./gotry web` | 持续多轮规划,看推理可视化 :3080 |
44
+ | headless 一问一答 | `./gotry "一句任务"` | 脚本 / CI / 定向调试 → stdout |
45
+ | help | `./gotry help` | 三行帮助 |
115
46
 
116
- <br>
47
+ 为什么源码模式要先装 runtime:dsh 以 vendored tarball 进 git(`ts/dsh-runtime/`),npm 一键分发不打包它;源码检出读 `.ts` 源码,装完一次重复可用。
117
48
 
118
- *One-liner mental model: `npx @danceiny/gotry@rc web` + `.env` 一行 key — done。dsh 对话框是唯一产品面(旧 shell 前端已删除)。*
49
+ </details>
119
50
 
120
- > 一行安装的关键点: `./gotry web`(终端粘贴即用),或 `./gotry "任务"`(一句问答)。**任何自然语言都能用**(这是 dsh runtime 投资的最大价值)。详见 [known limitations](#-known-limitations)。
51
+ ---
121
52
 
122
- ### 可选:启用登录态会话检索
53
+ ## ✨ 它做什么 — What it does
123
54
 
124
- 会话检索默认只读,并复用用户本人已登录的日常 Chrome。使用前需要 Chrome 144+:
55
+ GoTry 把「想去哪」变成「能不能、怎么去」:
125
56
 
126
- 1. 在日常 Chrome 打开 `chrome://inspect/#remote-debugging` 并启用 Remote Debugging;
127
- 2. 保持 Chrome 打开;GoTry 发起连接后,在 Chrome 弹出的权限框中确认连接;
128
- 3. 若遇到登录、OTP、验证码或 challenge,由用户本人处理,GoTry 不读取或代填这些信息。
57
+ | 阶段 | 由谁做 | 给你什么 |
58
+ |---|---|---|
59
+ | **动机访谈** | LLM | 必问项:工作窗口 / 已订资源 / 出发城市 |
60
+ | **事实抽取** | LLM | 工作窗口生效 + 休假语义识别 |
61
+ | **可行性判定** | **Z3 求解器** | 哪些候选可行、哪些不可行、为什么、**最小改动让它可行** |
62
+ | **门到门全成本** | 求解器 | 真实飞行时长(含时差)+ 醒来起夜惩罚 + 接驳代价 + 到达精力 % |
63
+ | **证据链** | 渲染层 | 每个数字带标签:`[骨架:openflights]` / `[实时API:flyai]` / `[静态包:估算]` |
129
64
 
130
- **仅打开该页面或启用开关不代表已经 attach 成功。** 必须等权限确认和 CDP 握手完成;在此之前会稳定返回 `needs-attach`,停止并且不消耗执行配额。
65
+ **不像普通 AI 聊天**——LLM 只做理解和解释,**判定与算术是数学求解器**算的。
131
66
 
67
+ > **Unlike a regular AI chat**, the LLM only translates and explains. **Decisions and arithmetic are computed by a Z3 solver**, not guessed.
132
68
 
133
69
  ---
134
70
 
135
71
  ## 🎬 一段对话 — Demo
136
72
 
137
- 输入 → 引擎判定 → 输出(真实截图,monospace 保留原味):
73
+ 输入 → 引擎判定 → 输出(真实输出,milliseconds 保留原味):
138
74
 
139
75
  ```
140
- > 我想去洱海边发呆两三天,上海出发,预算 3000,年假别让我办公。
76
+ > 我想去洱海边发呆两三天,上海出发,预算 3000,年假别让我办公。
141
77
 
142
78
  GoTry: 收到。先把约束记下来——
143
- • 窗口: 2 天(2026 年窗口)
144
- 出发: 上海
145
- • 预算: ¥3000 全含
146
- • 动机: 休整 / [escape_rest: 0.7]
147
- • 不要求工作窗口(年假)
148
- • 还没订任何东西
79
+ • 窗口: 2 天(2026 年窗口) • 出发: 上海 • 预算: ¥3000 全含
80
+ 动机: 休整 / [escape_rest: 0.7] • 还没订任何东西
149
81
 
150
82
  引擎判定:
151
-
152
- **大理·洱海: 现在不行** —— 冲突约束: duration。
153
- 你的 2 天窗口装不下「至少 5 天的洱海休整」(你之前的画像也有此约)
154
- 放宽方案: 把行程延长到 5 天,约 ¥4950。
155
- ★已放入「下一次出发」清单: 需 5 天起,3-5 / 9-11 月最佳。
156
-
157
- **千岛湖: 可行** (G7315 06:35 出发,bus 接驳,
158
- ¥996,起床 06:35,到达精力 84%,门到门 5h20m,
159
- 有效休整 4.4h)
160
- **太湖: 可行** (G101 09:00 出发,
161
- ¥716,起床 07:12,到达精力 84%,
162
- 有效休整 4.6h)
163
-
83
+ **大理·洱海: 现在不行** —— 2 天窗口装不下「至少 5 天的洱海休整」。
84
+ 放宽方案: 行程延长到 5 天,约 ¥4950。★已放入「下一次出发」清单。
85
+ **千岛湖: 可行** (G7315 06:35 出发,¥996,到达精力 84%,有效休整 4.4h)
86
+ **太湖: 可行** (G101 09:00 出发,¥716,有效休整 4.6h)
164
87
  建议: 千岛湖(意象匹配 80%)。
165
- 备选: 太湖(¥716,匹配 60%)。
166
-
167
- **待你决定**:
168
- 1. 千岛湖还是太湖?(前者更贴意象,后者更省)
169
- 2. 出发班次选 G7315(06:35)还是更晚的?
170
88
 
171
89
  [骨架:openflights] ✓ SZX↔PVG 已校验 [实时API:hbcli] 上海机场在跑
172
90
  [静态包:估算] G7315/G7316 价格按 7-8 月淡季估算
@@ -176,6 +94,39 @@ GoTry: 收到。先把约束记下来——
176
94
 
177
95
  ---
178
96
 
97
+ ## 🧰 18 个工具 — Tools
98
+
99
+ | 组 | 工具 | 干什么 |
100
+ |---|---|---|
101
+ | **实时检索(OTA/官方只读)** | `gotry_flyai_search` | 机票/火车/酒店实时报价(飞猪官方通道;酒店价格为上游打码展示,真实价以 jumpUrl 为准) |
102
+ | | `gotry_session_search` | 在**用户本人登录态**里查携程机票(授权后,只读) |
103
+ | | `gotry_session_login` | 登录引导:自动检测已登录与否,未登录才在用户 Chrome 弹登录入口(**零终端**) |
104
+ | | `gotry_weather_check` | Open-Meteo 预报≤16 天 + 历史气候基线 |
105
+ | | `gotry_flight_verify` | OpenSky ADS-B 航班实时观测(三值) |
106
+ | | `gotry_skeleton_check` | OpenFlights 168 对枢纽通航性(三值) |
107
+ | **库存与目录** | `gotry_hotel_search` | hotel-byte 实时桥,降级静态包(证据标注) |
108
+ | | `gotry_anything_search` | 城市/酒店/地标混合目录(hotel-be Anything) |
109
+ | **判定引擎** | `gotry_feasibility_check` | 门到门真成本可行性(Z3),逐候选判定 |
110
+ | **记忆与触达** | `gotry_motivation_save` | 动机画像落盘(evidence 强制,反幻觉) |
111
+ | | `gotry_wish_pool_add` / `gotry_wish_pool_list` | 「下一次出发」愿望池 + 0..1 条件召回 |
112
+ | | `gotry_companion_save` · `gotry_trip_log` | 同行人档案 / 旅行时间线 |
113
+ | **通用外部** | `gotry_web_search` · `gotry_video_subtitle` · `gotry_github_search` · `gotry_agent_reach` | 网页/字幕/GitHub/全渠道外部信息(经 Agent-Reach) |
114
+
115
+ ---
116
+
117
+ ## 🔐 账号会话:授权与隐私 — Account consent
118
+
119
+ 会话检索使用**你本人已登录的 Chrome**读取酒店/机票实时数据,为此立了四条 hard 规则:
120
+
121
+ 1. **登录在外部网站完成** —— gotry 从不提供、不代填、不收集任何密码/验证码/cookie 值。它只回答一个布尔问题:"登录票据 cookie 存在吗"(只读**名字**,0 值过手)。
122
+ 2. **授权卡,每会话一次** —— 首次动用账号会话会弹运行时审批卡;批准后会话内记住,拒绝即本会话吊销,不再打扰。总闸 `sessionAccess: ask|allow|off` 随时可关。
123
+ 3. **物理只读** —— ReadGuard 在网络层中止一切写请求(下单/支付在传输层不可达),agent 永不接触凭证与验证码;遇到验证码立即停,交还给你。
124
+ 4. **绝不劫持你的浏览器** —— 检索/登录只开自己的独立标签页,登录页置前台、留在你那;例行动测试永不自动开浏览器窗。
125
+
126
+ > 前置(一次性):日常 Chrome 打开 `chrome://inspect/#remote-debugging` 打开开关(Chrome 144+),并保持 Chrome 在线。未开启时工具返回 `needs-attach` 并给出指引,不消耗执行配额。
127
+
128
+ ---
129
+
179
130
  ## 🏛️ 架构 — Architecture
180
131
 
181
132
  ```
@@ -190,26 +141,37 @@ GoTry: 收到。先把约束记下来——
190
141
 
191
142
  | 层 | 模块 | 角色 |
192
143
  |---|---|---|
193
- | L2 | `ts/src/index.ts` (dsh 插件) | 注册 18 个工具,挂 `{{current_date}}`/`{{time_anchor_card}}`/`{{motivation_brief}}` 变量,工具 execute 异常隔离 + 平铺观察 envelope + uncaughtException 护栏;账号会话工具挂授权闸(`tools/pre-execute` → 运行时审批卡,每会话首次请求、拒绝即会话内吊销,`sessionAccess: ask\|allow\|off` 总闸) |
194
- | L3 | `ts/src/unified.ts` · `py/gotry_feasibility/unified.py` | 唯一求解入口(候选枚举 + 航班链 Z3) |
195
- | L4 | `ts/capabilities/hbcli.ts` | hotelbyte-cli 进程封装 + 降级到 `data/hotels_2026.json` |
196
- | L4 | `ts/scripts/skeleton-check.ts` | OpenFlights 168 对枢纽,三值语义(阳性/枢纽对否定≠证伪/枢纽外=无结论) |
197
-
198
- > 📖 完整 ADR / 演进阶段 / 债务清单见 [`docs/architecture.md`](docs/architecture.md)。
144
+ | L2 | `ts/src/index.ts`(dsh 插件) | 注册 18 工具,挂时间锚点/记忆 brief 变量;execute 异常隔离 + 授权闸 + 进程护栏 |
145
+ | L3 | `ts/src/unified.ts` · `py/gotry_feasibility/` | 唯一求解入口(候选枚举 + 航班链 Z3) |
146
+ | L4 | `ts/capabilities/hbcli.ts` · `skeleton-check.ts` | 实时库存桥 + OpenFlights 骨架(三值语义) |
147
+ | L5 | loopx 治理面 | objective / gates / evidence / quota |
199
148
 
149
+ > 📖 完整 ADR / 演进 / 债务清单: [`docs/architecture.md`](docs/architecture.md)
200
150
 
201
151
  ---
202
152
 
203
- ## ⚠️ Known limitations · 已知限制
153
+ ## ⚠️ 状态与限制 Status & limitations
204
154
 
205
- - ✅ **License MIT** (D-1 落地 2026-08-23)
206
- - ✅ **英文面(工程层)已就绪** — 求解确定性输出面(候选/航班链 answer_md、放宽建议、排除理由)i18n catalog 化:`GOTRY_LOCALE=en` 或 `setLocale('en')` 即英文,默认 zh-CN 与金标准逐字节一致(run-all §32:en 零缺键/切换数据不动)。**残余(诚实声明)**:dsh web 界面属宿主、工具结果卡与人格对话面等 M4 校准输入落定后随校准样本补齐——种子市场仍是中国出境(G1)
207
- - ✅ **机票实时数据已接入** — dated 航班链段可经 FlyAI 官方只读通道取实时票价覆写(`GOTRY_REALTIME_PRICING=1`,证据链 `[实时API:flyai@ts]`,miss/error/打码价一律降级回静态包 `data/flights_2026.json`);静态包由「唯一来源」变为「显式降级」,零 key 可用
208
- - ✅ **酒店实时报价已接入(OTA 平铺)** — `gotry_flyai_search` kind=hotel 直连飞猪官方 `search-hotel`(2026-08-29 实测:结构化 名称/档级/打码价);未鉴权价格上游打码(如 ¥7xx),工具保 priceRaw 原值不伪装成数字价,真实价以 jumpUrl 酒店页为准;OTA 工具面平铺,无「主链路/降级」路由——按查询取用,证据链逐源标注
209
- - ✅ **账号登录已产品化(第 18 工具)** — `gotry_session_login`:会话检索遇 `needs-login` 时 agent 直接调它,在你的 Chrome 弹出携程登录页、等你在**携程官网**完成登录——全程无需终端。**登录永远发生在外部网站、不在 gotry 里进行;gotry 永不收集/存储/传输密码、验证码或任何 cookie 值,只读「票据 cookie 是否存在」这一个事实(名称级,0 值过手)**。授权闸 v2:每会话首次调用弹审批卡、会话内记住;拒绝 = 本会话吊销;`sessionAccess: ask|allow|off` 总闸随时可关(session-tests §I/§J + smoke §13 验证)
210
- - ✅ **Z3 WASM race 已根治** — 三模块各自持 WASM 实例 + 候选求解 `Promise.all` 共 Context 并发是双重根因;现收敛 `ts/src/z3-shared.ts`(单一实例+单一 Context+会话级互斥门),run-all §1 的「重试一次」止血已退役,新增 §30 进程内三形态并发回归闸 ×12(D-17)
211
- - ✅ **薄壳已删除** — `shell/` 目录与 `./gotry shell` 分支已移除(2026-08-29 清理;dsh web 唯一产品面,旧命令行在 git 历史/rc.6 文档内可考)
155
+ **已就绪(全绿可验)**:
212
156
 
157
+ - ✅ **Z3 WASM race 已根治**(`z3-shared.ts` 单实例+会话级互斥;run-all §30 并发回归闸)
158
+ - ✅ **实时机票/酒店/会话检索**已接入:FlyAI 官方通道(机/火/酒;`GOTRY_REALTIME_PRICING=1` 可选把实时票价覆写进求解)
159
+ - ✅ **英文面(工程层)**:`GOTRY_LOCALE=en` 一键切换,en 零缺键,金标准行为不变
160
+ - ✅ **账号会话:授权闸 + 登录产品化 + 自动检测**(见上方 [🔐 账号会话](#-账号会话授权与隐私--account-consent))
161
+ - ✅ License MIT;薄壳遗留已删除(dsh web 唯一产品面)
162
+
163
+ **未收口(诚实清单)**:
164
+
165
+ - ⏳ **M3 Exit 未关闭** —— 工程与分发面就绪,但真实种子用户 evidence(50–200 人 cohort)未积累;合成 fixture 只证明合同不证明 business pass
166
+ - ⏳ **中文之外的界面残余** —— dsh web 界面属宿主;工具结果卡与人格对话面的英文校准件挂 M4 校准样本
167
+ - ⏳ **携程酒店/美团会话适配器** —— 机票已通,酒店会话面等登录态实测回填(下一 tick)
168
+
169
+ <details>
170
+ <summary>📖 展开完整状态表(账本/证据合同/里程碑口径)</summary>
171
+
172
+ 事务化状态账本(ADR-15,`gotry_async_terminal.v1`:4/4→`succeeded`/ledger `settled`/exit 0,非 4/4→`failed`/`failed`/exit 2,终态复诵零重算);双形态架构冻结(ADR-16:本地+Web 一套账本语义,tenant_id 一等字段);会话数据面 #21 的字段 fixture scorer、双源合同与 waiting-attach no-spend 已进确定性回归,真实 sf-01..08 尚未验收;里程碑口径(2026-08-29):M3 工程与分发面已就绪但真实种子用户 evidence 未收口,M4 由 founder 授权并行推进不构成 M3 Exit 证明,M5/M6 仅在各自 Entry gate 满足后启动——细则见 [`docs/roadmap.md`](docs/roadmap.md) 与 [`docs/architecture.md`](docs/architecture.md)。
173
+
174
+ </details>
213
175
 
214
176
  ---
215
177
 
@@ -226,7 +188,6 @@ GoTry: 收到。先把约束记下来——
226
188
  | [`docs/kimi-postmortem.md`](docs/kimi-postmortem.md) | 所有人:真实 AI 旅行规划失败复盘(反面教材) |
227
189
  | [`docs/release-notes.md`](docs/release-notes.md) | 历史:每个 tag 的发布闸勾稽 |
228
190
 
229
-
230
191
  ---
231
192
 
232
193
  ## 🧪 跑测试 — Verify
@@ -235,11 +196,7 @@ GoTry: 收到。先把约束记下来——
235
196
  ./scripts/run-all-tests.sh
236
197
  ```
237
198
 
238
- 全栈一次性绿(纯 TS,无 Python 依赖):engine/journey/unified 金标准 · 对话重放 ·
239
- 异步工单跨进程 · 插件 smoke · hbcli · 进程护栏(含工具异常隔离) · 天气 · 航班 ·
240
- Anything · probePoi · agent-reach(web/deep/wrapper)· 双路径稳定性 ·
241
- 时间感评测(锚点卡/槽位过期校验/评分器/mock 回放;真模型巡检 `time-eval-tests.ts --real`)·
242
- 记忆域(动机合并守门/效用 sidecar/只读指标投影)· **Z3 并发竞态(§30)· 实时票价桥(§31)· i18n 目录(§32)· M3 cohort 证据合同(§33)· M4 价值证据合同(§34)**。
199
+ 全栈一次性绿(纯 TS,无 Python 依赖):engine/journey/unified 金标准 · 对话重放 · 异步工单跨进程 · 插件 smoke · hbcli · 进程护栏(含工具异常隔离)· 天气 · 航班 · Anything · probePoi · agent-reach(web/deep/wrapper)· 双路径稳定性 · 时间感评测(锚点卡/槽位过期校验/评分器/mock 回放;真模型巡检 `time-eval-tests.ts --real`)· 记忆域(动机合并守门/效用 sidecar/只读指标投影)· **Z3 并发竞态(§30)· 实时票价桥(§31)· i18n 目录(§32)· M3 cohort 证据合同(§33)· M4 价值证据合同(§34)**。
243
200
 
244
201
  ---
245
202
 
@@ -247,21 +204,17 @@ Anything · probePoi · agent-reach(web/deep/wrapper)· 双路径稳定性 ·
247
204
 
248
205
  > *PR-based flow: branch off the latest `main`, full suite green, open a Pull Request — `main` never takes direct pushes; merge after review. Full guide: [CONTRIBUTING.md](CONTRIBUTING.md).*
249
206
 
250
- 标准开源流程:**`main` 不直接推**——从最新 `main` 切出 `feat/ · fix/ · docs/ · chore/` 分支,本地全栈绿后开 Pull Request,CINode 22/24,typecheck + 全部套件)与维护者 review 双绿后 squash 合入。**测试红着不许合。**
207
+ 标准开源流程:**`main` 不直接推**——从最新 `main` 切出 `feat/ · fix/ · docs/ · chore/` 分支,本地全栈绿后开 Pull Request,CI(Node 22/24,typecheck + 全部套件)与维护者 review 双绿后 squash 合入。**测试红着不许合。**
251
208
 
252
209
  - 完整贡献指南(环境搭建 · 测试 · 分支与提交约定 · PR 流程): **[CONTRIBUTING.md](CONTRIBUTING.md)**
253
210
  - Bug / 功能建议:[issue 模板](.github/ISSUE_TEMPLATE/bug_report.yml)(搜过既有 issue 再提)
254
211
  - 行为或架构改动先立 ADR(`docs/architecture.md` §8);多 agent 协作契约见 [`AGENTS.md`](AGENTS.md)
255
212
 
256
- 单套件快捷命令等测试细节见上面的 [🧪 跑测试 — Verify](#-跑测试--verify)。
257
-
258
213
  ---
259
214
 
260
215
  ## 📜 License
261
216
 
262
- **MIT**(2026-08-23 落定)。与上游 dshMIT)/ loopx 一致——宽松、可商用、可闭源分叉。
263
-
264
- 文本见 [LICENSE](LICENSE)。
217
+ **MIT**(2026-08-23 落定)。与上游 dsh(MIT)/ loopx 一致——宽松、可商用、可闭源分叉。文本见 [LICENSE](LICENSE)。
265
218
 
266
219
  ---
267
220
 
@@ -269,10 +222,10 @@ Anything · probePoi · agent-reach(web/deep/wrapper)· 双路径稳定性 ·
269
222
 
270
223
  - **English section summaries** are inline above (italic blockquotes).
271
224
  - 本 README 主语言是中文,面向中国出境首发种子用户群。
272
- - 完整英文版 README 计划在 v0.1.0 同步([issue 路线](#-known-limitations))。
225
+ - 完整英文版 README 计划在 v0.1.0 同步。
273
226
 
274
227
  ---
275
228
 
276
- **Built with**: DeepSeek Harness 0.1.2-alpha.1 (vendored) · Cordis · Z3 (WASM) · loopx (pipx) · hotelbyte-cli · Agent-Reach v1.5.0 (`.venv/`) · OpenFlights · TypeScript · Bun
229
+ **Built with**: DeepSeek Harness 0.1.2-alpha.1 (vendored) · Cordis · Z3 (WASM) · loopx (pipx) · hotelbyte-cli · Agent-Reach v1.5.0 (`.venv/`) · OpenFlights · TypeScript
277
230
 
278
- **Last verified against `v0.0.1-rc.11`(已发布,2026-08-29)** — 全栈回归全绿 §1-§32(发布流程见 `scripts/publish-npm.sh`,内部决策见 [docs/decisions-needed.md](docs/decisions-needed.md)、[docs/tokens.md](docs/tokens.md))
231
+ **Last verified against `v0.0.1-rc.13`(已发布,2026-08-29)** — 全栈回归全绿 §1-§34(发布流程见 `scripts/publish-npm.sh`)。
@@ -84,8 +84,8 @@
84
84
  登录态,现携程机票):**首次调用**经运行时审批卡向用户请求授权,本会话
85
85
  内记住、不再重复弹卡;用户拒绝即本会话吊销——不再请求也不再执行,改走
86
86
  其他工具推进;无审批通道(headless)不执行。返回 needs-login→调
87
- gotry_session_login 产品工具(在用户自己的 Chrome 打开携程登录入口并等待
88
- 其完成登录,不需要任何终端命令;**登录永远发生在携程官网、不在 gotry
87
+ gotry_session_login 产品工具(先自动检测票据——已登录则直接确认零弹窗;未登录才
88
+ 打开登录入口等待其完成,不需要任何终端命令;**登录永远发生在携程官网、不在 gotry
89
89
  里进行**——gotry 永不收集/存储/传输密码、验证码或任何 cookie 值,只检查
90
90
  "是否已登录"这一个事实;向用户说明这一点),完成前用官方源推进,不反复
91
91
  开页探测;返回 challenged→立即停手如实告知,绝不重试
@@ -87,7 +87,14 @@ export async function openSession(opts = {}) {
87
87
  };
88
88
  }
89
89
  }
90
- const page = (await browser.pages())[0] ?? await browser.newPage();
90
+ let ownPage = false;
91
+ let page;
92
+ if (opts.newPage) {
93
+ page = await browser.newPage();
94
+ ownPage = true;
95
+ } else {
96
+ page = (await browser.pages())[0] ?? await browser.newPage();
97
+ }
91
98
  return {
92
99
  ok: true,
93
100
  browser,
@@ -97,6 +104,9 @@ export async function openSession(opts = {}) {
97
104
  requestCount: ()=>0
98
105
  },
99
106
  close: async ()=>{
107
+ if (ownPage && (opts.closeOwnPage ?? true)) {
108
+ await page.close().catch(()=>{});
109
+ }
100
110
  await (isCdp ? browser.disconnect() : browser.close()).catch(()=>{});
101
111
  }
102
112
  };
@@ -57,12 +57,28 @@ export async function sessionLogin(q = {}) {
57
57
  }
58
58
  const evidenceTag = `[会话:${site}-login@${ts}]`;
59
59
  try {
60
+ const pre = await pollTicketNames(t.browser, target);
61
+ if (pre.length > 0) {
62
+ await t.close();
63
+ return {
64
+ ok: true,
65
+ via: 'session-login',
66
+ latencyMs: Date.now() - started,
67
+ verdict: 'logged-in',
68
+ site,
69
+ tickets: pre,
70
+ evidence: `${evidenceTag} 自动检测:票据 cookie 已在(先前登录已生效)——[${pre.join(', ')}](只读名字,0 网页交互)`
71
+ };
72
+ }
73
+ const page = await t.browser.newPage();
74
+ await page.bringToFront().catch(()=>{});
60
75
  try {
61
- await t.page.goto(target.entryUrl, {
76
+ await page.goto(target.entryUrl, {
62
77
  waitUntil: 'domcontentloaded',
63
78
  timeout: 30_000
64
79
  });
65
80
  } catch {}
81
+ console.log(`[session-login] ${target.label} 登录入口已在新标签页置前打开`);
66
82
  const waitMs = Math.min(Math.max(q.waitMs ?? 90_000, 0), 300_000);
67
83
  const pollMs = Math.min(Math.max(q.pollMs ?? 3_000, 500), 10_000);
68
84
  const deadline = Date.now() + waitMs;
@@ -34,7 +34,8 @@ export async function sessionFlightSearch(q) {
34
34
  profileDir: q.profileDir,
35
35
  headless: q.headless,
36
36
  auditPath: q.auditPath,
37
- mode: q.profileDir ? 'persistent' : 'cdp'
37
+ mode: q.profileDir ? 'persistent' : 'cdp',
38
+ newPage: true
38
39
  });
39
40
  if (!t.ok) {
40
41
  return err(classifyTransportFailure(t.summary, q.profileDir === undefined), t.summary);
@@ -387,6 +387,8 @@ console.log('H. flyaiSearch hotel(飞猪官方 search-hotel)');
387
387
  });
388
388
  if (lr.verdict === 'needs-attach') {
389
389
  assert(lr.ok === false && /chrome:\/\/inspect/.test(lr.error ?? ''), 'live:Chrome 未开调试 → needs-attach + 一次性指引', lr);
390
+ } else if (lr.verdict === 'logged-in') {
391
+ assert(lr.ok === true && (lr.tickets?.length ?? 0) > 0, 'live:已登录自动检测 → logged-in(零弹窗,票据名级)', lr);
390
392
  } else {
391
393
  assert(lr.ok === true && lr.verdict === 'pending', 'live:attach 成功不交互 → pending(入口已开,等人登录)', lr);
392
394
  const evidenceTagRe = /\[会话:ctrip-flight-login@/;
package/dist/src/index.js CHANGED
@@ -906,7 +906,7 @@ export function apply(ctx, config) {
906
906
  }));
907
907
  registerGuarded(defineTool({
908
908
  name: 'gotry_session_login',
909
- description: 'Productized login bootstrap for the account session channel (call this when gotry_session_search returns needs-login — the user never needs a terminal). ' + 'OPENS the site login entry in the USER\'S OWN Chrome and waits for the user to finish logging in on the external site. ' + 'GoTry NEVER collects, stores, or transmits credentials: no passwords, no SMS codes, no cookie values — it only checks the boolean fact "already logged in" (reads cookie NAMES only, zero values). ' + 'verdict logged-in (tickets detected) | pending (login tab opened, user not done yet — offer to re-check later) | needs-attach (one-time Chrome remote-debugging switch instructions). ' + 'Evidence [会话:<site>-login@ts].',
909
+ description: 'Productized login bootstrap for the account session channel (call this when gotry_session_search returns needs-login — the user never needs a terminal). ' + 'AUTO-DETECTION FIRST: it reads ticket-cookie NAMES before anything else — if the user already logged in (on the external site) it confirms instantly WITHOUT opening any page. ' + 'OPENS the site login entry in the USER\'S OWN Chrome and waits for the user to finish logging in on the external site. ' + 'GoTry NEVER collects, stores, or transmits credentials: no passwords, no SMS codes, no cookie values — it only checks the boolean fact "already logged in" (reads cookie NAMES only, zero values). ' + 'verdict logged-in (tickets detected) | pending (login tab opened, user not done yet — offer to re-check later) | needs-attach (one-time Chrome remote-debugging switch instructions). ' + 'Evidence [会话:<site>-login@ts].',
910
910
  parameters: {
911
911
  query: {
912
912
  type: 'json',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danceiny/gotry",
3
- "version": "0.0.1-rc.12",
3
+ "version": "0.0.1-rc.13",
4
4
  "description": "GoTry — 从出发到下一次出发的 AI 旅行 Agent(dsh 插件)。npm 包入口 + vendored dsh runtime + 5 行 README 安装路径。",
5
5
  "type": "module",
6
6
  "main": "ts/src/index.ts",
@@ -57,6 +57,14 @@ export interface TransportOptions {
57
57
  * 凭证流的中间(隐私+可靠性双输)。该形态下不发起任何检索导航、不落审计。
58
58
  */
59
59
  guard?: boolean
60
+ /**
61
+ * 开**自己的新标签页**(默认 false=沿用既有首页——那是用户的页面!)。
62
+ * 人机共治纪律(2026-08-29 founder:「我根本就看不到登录页面」):登录与检索一律
63
+ * newPage 开自己的页,绝不劫持用户已有标签页;closeOwnPage 控制收尾是否关掉自己开的页。
64
+ */
65
+ newPage?: boolean
66
+ /** newPage:true 时,close() 是否连自己开的标签页一起关(默认 true;登录引导保持 false——把登录页留给用户) */
67
+ closeOwnPage?: boolean
60
68
  }
61
69
 
62
70
  function devtoolsWsEndpoint(): { ws: string } | { err: string } {
@@ -111,14 +119,27 @@ export async function openSession(opts: TransportOptions = {}): Promise<SessionT
111
119
  return { ok: false, summary: `read-guard attach failed: ${e instanceof Error ? e.message.split('\n')[0] : String(e)}` }
112
120
  }
113
121
  }
114
- const page = (await browser.pages())[0] ?? (await browser.newPage())
122
+ // 标签页纪律:ownPage=自己开的新页(newPage:true)——用它,收尾按 closeOwnPage 关;
123
+ // 否则沿用既有首页(仅 persistent 兼容形态;cdp 检索/登录一律 newPage)
124
+ let ownPage: boolean = false
125
+ let page
126
+ if (opts.newPage) {
127
+ page = await browser.newPage()
128
+ ownPage = true
129
+ } else {
130
+ page = (await browser.pages())[0] ?? await browser.newPage()
131
+ }
115
132
  return {
116
133
  ok: true,
117
134
  browser,
118
135
  page,
119
136
  guard: guard ?? { blockedCount: () => 0, requestCount: () => 0 },
120
137
  close: async () => {
138
+ // 自己开的标签页按 closeOwnPage 收尾(登录引导保持 false——登录页留给用户);
121
139
  // cdp:只断开连接,绝不关用户浏览器;persistent:关自己拉起的实例
140
+ if (ownPage && (opts.closeOwnPage ?? true)) {
141
+ await (page as { close(): Promise<void> }).close().catch(() => { /* ignore */ })
142
+ }
122
143
  await (isCdp ? browser.disconnect() : browser.close()).catch(() => { /* ignore */ })
123
144
  },
124
145
  }
@@ -84,6 +84,8 @@ export async function sessionLogin(q: SessionLoginQuery = {}): Promise<SessionLo
84
84
  if (!target) {
85
85
  return err(site, 'error', `未知站点 ${site}(可选 ${Object.keys(LOGIN_TARGETS).join('/')})`, started, ts)
86
86
  }
87
+ // 人机共治纪律:开自己的新标签页并置前台——用户必须看见登录页(2026-08-29 founder:
88
+ // 「我根本就看不到登录页面」,此前误劫持用户既有标签页);closeOwnPage=false 把登录页留给用户
87
89
  const t = await openSession({ mode: 'cdp', guard: false })
88
90
  if (!t.ok) {
89
91
  const needsAttach = /chrome:\/\/inspect|DevToolsActivePort|cdp attach 失败/.test(t.summary)
@@ -98,11 +100,25 @@ export async function sessionLogin(q: SessionLoginQuery = {}): Promise<SessionLo
98
100
  }
99
101
  const evidenceTag = `[会话:${site}-login@${ts}]`
100
102
  try {
103
+ // 自动检测优先(2026-08-29 founder「UI 里有自动检测吗」):先只读票据名——
104
+ // 已登录则**零弹窗**直接确认,不打开任何页面;登录状态由用户在携程官网自然留存
105
+ const pre = await pollTicketNames(t.browser as unknown as { cookies(): Promise<CookieLike[]> }, target)
106
+ if (pre.length > 0) {
107
+ await t.close()
108
+ return {
109
+ ok: true, via: 'session-login', latencyMs: Date.now() - started, verdict: 'logged-in', site, tickets: pre,
110
+ evidence: `${evidenceTag} 自动检测:票据 cookie 已在(先前登录已生效)——[${pre.join(', ')}](只读名字,0 网页交互)`,
111
+ }
112
+ }
113
+ // 未检出 → 开自己的新标签页并置前台(绝不劫持用户既有页面),等待用户在官网完成登录
114
+ const page = await t.browser.newPage()
115
+ await (page as unknown as { bringToFront(): Promise<void> }).bringToFront().catch(() => { /* 某些环境不可置前 */ })
101
116
  try {
102
- await t.page.goto(target.entryUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 })
117
+ await page.goto(target.entryUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 })
103
118
  } catch {
104
119
  // 站点慢不致命:入口标签已打开,用户手动登录即可
105
120
  }
121
+ console.log(`[session-login] ${target.label} 登录入口已在新标签页置前打开`)
106
122
  const waitMs = Math.min(Math.max(q.waitMs ?? 90_000, 0), 300_000)
107
123
  const pollMs = Math.min(Math.max(q.pollMs ?? 3_000, 500), 10_000)
108
124
  const deadline = Date.now() + waitMs
@@ -74,7 +74,8 @@ export async function sessionFlightSearch(q: SessionFlightQuery): Promise<Sessio
74
74
  return err('error', `unresolved entry: ${(entry.unresolved ?? []).join('/')} 不在城市码表`)
75
75
  }
76
76
 
77
- const t = await openSession({ profileDir: q.profileDir, headless: q.headless, auditPath: q.auditPath, mode: q.profileDir ? 'persistent' : 'cdp' })
77
+ // 人机共治纪律:检索一律开自己的新标签页(绝不劫持用户已有页面),用完关自己的页
78
+ const t = await openSession({ profileDir: q.profileDir, headless: q.headless, auditPath: q.auditPath, mode: q.profileDir ? 'persistent' : 'cdp', newPage: true })
78
79
  if (!t.ok) {
79
80
  // cdp 未开端口或握手失败 → needs-attach(一次性用户动作);persistent 启动失败仍走 error。
80
81
  // transport 的“端口未开”在连接前返回,文案不含 `cdp attach 失败`,两种形态都要归入同一用户门禁。
package/ts/src/index.ts CHANGED
@@ -736,7 +736,7 @@ export function apply(ctx: Context, config: Config): void {
736
736
  registerGuarded(defineTool({
737
737
  name: 'gotry_session_login',
738
738
  description:
739
- 'Productized login bootstrap for the account session channel (call this when gotry_session_search returns needs-login — the user never needs a terminal). '
739
+ 'Productized login bootstrap for the account session channel (call this when gotry_session_search returns needs-login — the user never needs a terminal). ' + 'AUTO-DETECTION FIRST: it reads ticket-cookie NAMES before anything else — if the user already logged in (on the external site) it confirms instantly WITHOUT opening any page. '
740
740
  + 'OPENS the site login entry in the USER\'S OWN Chrome and waits for the user to finish logging in on the external site. '
741
741
  + 'GoTry NEVER collects, stores, or transmits credentials: no passwords, no SMS codes, no cookie values — it only checks the boolean fact "already logged in" (reads cookie NAMES only, zero values). '
742
742
  + 'verdict logged-in (tickets detected) | pending (login tab opened, user not done yet — offer to re-check later) | needs-attach (one-time Chrome remote-debugging switch instructions). '