@danceiny/gotry 0.0.1-rc.10
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/LICENSE +21 -0
- package/README.md +267 -0
- package/bin/gotry-inner.js +214 -0
- package/bin/gotry-stdio-ask.js +59 -0
- package/bin/gotry.js +28 -0
- package/cordis.gotry-patch.yml +86 -0
- package/data/flights_2026.json +218 -0
- package/data/golden_erhai.json +92 -0
- package/data/golden_trip_2026.json +75 -0
- package/data/hotels_2026.json +151 -0
- package/data/openflights-skeleton.json +895 -0
- package/data/time-slot-eval.json +384 -0
- package/data/yunnan-pack.json +202 -0
- package/dist/capabilities/agent-reach-bridge.py +90 -0
- package/dist/capabilities/agent-reach-deep.js +172 -0
- package/dist/capabilities/agent-reach.js +204 -0
- package/dist/capabilities/anything.js +139 -0
- package/dist/capabilities/flyai.js +131 -0
- package/dist/capabilities/hbcli.js +143 -0
- package/dist/capabilities/incident-log.js +100 -0
- package/dist/capabilities/opensky.js +74 -0
- package/dist/capabilities/session/action-cache.js +120 -0
- package/dist/capabilities/session/adapters/ctrip-flight.js +83 -0
- package/dist/capabilities/session/adapters/meituan-local.js +73 -0
- package/dist/capabilities/session/extract.js +40 -0
- package/dist/capabilities/session/read-guard.js +99 -0
- package/dist/capabilities/session/transport.js +101 -0
- package/dist/capabilities/session-search.js +93 -0
- package/dist/capabilities/weather.js +180 -0
- package/dist/scripts/action-cache-tests.js +107 -0
- package/dist/scripts/agent-reach-deep-tests.js +54 -0
- package/dist/scripts/agent-reach-tests.js +38 -0
- package/dist/scripts/agent-reach-wrapper-tests.js +79 -0
- package/dist/scripts/anything-tests.js +95 -0
- package/dist/scripts/async-collect.js +37 -0
- package/dist/scripts/companion-tests.js +112 -0
- package/dist/scripts/diff-test.js +39 -0
- package/dist/scripts/engine-run.js +14 -0
- package/dist/scripts/engine-tests.js +45 -0
- package/dist/scripts/hbcli-tests.js +101 -0
- package/dist/scripts/incident-tests.js +96 -0
- package/dist/scripts/journey-tests.js +45 -0
- package/dist/scripts/ledger-tests.js +365 -0
- package/dist/scripts/ledger-workflow-crash.js +42 -0
- package/dist/scripts/memory-capture-tests.js +77 -0
- package/dist/scripts/memory-decay-tests.js +83 -0
- package/dist/scripts/memory-metrics.js +24 -0
- package/dist/scripts/nudge-digest.js +83 -0
- package/dist/scripts/opensky-check.js +20 -0
- package/dist/scripts/opensky-tests.js +52 -0
- package/dist/scripts/probe-poi-tests.js +88 -0
- package/dist/scripts/publish-preverify.js +68 -0
- package/dist/scripts/replay-async.js +44 -0
- package/dist/scripts/replay-real.js +38 -0
- package/dist/scripts/replay.js +116 -0
- package/dist/scripts/session-attach-diagnose.js +27 -0
- package/dist/scripts/session-attach-poc.js +65 -0
- package/dist/scripts/session-extract-tests.js +81 -0
- package/dist/scripts/session-tests.js +162 -0
- package/dist/scripts/skeleton-check.js +37 -0
- package/dist/scripts/skeleton-integration-test.js +22 -0
- package/dist/scripts/skills-contract-tests.js +85 -0
- package/dist/scripts/smoke.js +328 -0
- package/dist/scripts/state-cli-tests.js +136 -0
- package/dist/scripts/state-cli.js +248 -0
- package/dist/scripts/time-eval-tests.js +318 -0
- package/dist/scripts/travel-timeline-tests.js +123 -0
- package/dist/scripts/unified-tests.js +49 -0
- package/dist/scripts/weather-tests.js +48 -0
- package/dist/src/bridge.js +34 -0
- package/dist/src/companions.js +112 -0
- package/dist/src/contracts.js +64 -0
- package/dist/src/dsh-llm.js +174 -0
- package/dist/src/engine.js +331 -0
- package/dist/src/index.js +1239 -0
- package/dist/src/journey.js +147 -0
- package/dist/src/loop.js +360 -0
- package/dist/src/memory-capture.js +40 -0
- package/dist/src/memory-decay.js +31 -0
- package/dist/src/memory-utility.js +55 -0
- package/dist/src/mock-llm.js +103 -0
- package/dist/src/model.js +134 -0
- package/dist/src/slot-spec.js +165 -0
- package/dist/src/state-ledger.js +901 -0
- package/dist/src/time-anchor.js +100 -0
- package/dist/src/tool-packet.js +12 -0
- package/dist/src/travel-slots.js +144 -0
- package/dist/src/travel-timeline.js +78 -0
- package/dist/src/unified.js +538 -0
- package/dist/src/wish-pool.js +27 -0
- package/package.json +84 -0
- package/ts/capabilities/agent-reach-deep.ts +114 -0
- package/ts/capabilities/agent-reach.ts +158 -0
- package/ts/capabilities/anything.ts +201 -0
- package/ts/capabilities/flyai.ts +151 -0
- package/ts/capabilities/hbcli.ts +152 -0
- package/ts/capabilities/incident-log.ts +148 -0
- package/ts/capabilities/opensky.ts +121 -0
- package/ts/capabilities/session/action-cache.ts +148 -0
- package/ts/capabilities/session/adapters/ctrip-flight.ts +107 -0
- package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
- package/ts/capabilities/session/extract.ts +54 -0
- package/ts/capabilities/session/read-guard.ts +141 -0
- package/ts/capabilities/session/transport.ts +114 -0
- package/ts/capabilities/session-search.ts +129 -0
- package/ts/capabilities/weather.ts +176 -0
- package/ts/package.json +27 -0
- package/ts/scripts/async-collect.ts +50 -0
- package/ts/scripts/skeleton-check.ts +49 -0
- package/ts/scripts/skeleton-integration-test.ts +23 -0
- package/ts/scripts/state-cli.ts +201 -0
- package/ts/src/bridge.ts +44 -0
- package/ts/src/companions.ts +113 -0
- package/ts/src/contracts.ts +165 -0
- package/ts/src/dsh-llm.ts +155 -0
- package/ts/src/index.ts +968 -0
- package/ts/src/loop.ts +411 -0
- package/ts/src/memory-capture.ts +59 -0
- package/ts/src/memory-decay.ts +55 -0
- package/ts/src/memory-utility.ts +70 -0
- package/ts/src/mock-llm.ts +100 -0
- package/ts/src/model.ts +220 -0
- package/ts/src/slot-spec.ts +172 -0
- package/ts/src/state-ledger.ts +849 -0
- package/ts/src/time-anchor.ts +134 -0
- package/ts/src/tool-packet.ts +32 -0
- package/ts/src/travel-slots.ts +219 -0
- package/ts/src/travel-timeline.ts +83 -0
- package/ts/src/unified.ts +582 -0
- package/ts/src/wish-pool.ts +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Danceiny (GoTry)
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# GoTry
|
|
2
|
+
|
|
3
|
+
> **身体和灵魂,更多旅行,更少旅游。**
|
|
4
|
+
> *Body and soul — more travel, less tourism.*
|
|
5
|
+
|
|
6
|
+
**GoTry 是「从出发到下一次出发」的 AI 旅行 Agent**:你用一句话说想去哪、为什么想出发;它先问清楚你的工作时间和已订资源,再用**数学求解器**给你一份经过验证的行程方案。
|
|
7
|
+
|
|
8
|
+
**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.
|
|
9
|
+
|
|
10
|
+
| | |
|
|
11
|
+
|---|---|
|
|
12
|
+
| **Version** | `v0.0.1-rc.9`(npm **latest 直指本版**,`npx @danceiny/gotry` 即得;[release notes](docs/release-notes.md)) |
|
|
13
|
+
| **Status** | M4 记忆域推进中(2026-08-28):记忆写读闭环 + 效用 sidecar + 「下一次出发」0::1 召回;17 工具;事务化状态账本(ADR-15)+ 双形态架构冻结(ADR-16:本地+Web 一套账本语义,tenant_id 一等字段);全栈回归全绿(套件清单见 `scripts/run-all-tests.sh` 分节) |
|
|
14
|
+
| **Repo** | [github.com/Danceiny/gotry](https://github.com/Danceiny/gotry) (private) |
|
|
15
|
+
| **Runtime** | DeepSeek Harness 0.1.1-rc.2 (vendored `ts/dsh-runtime/`, [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`) |
|
|
16
|
+
| **License** | **MIT** (2026-08-23 落定,见 [LICENSE](LICENSE)) |
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## ✨ 它做什么 — What it does
|
|
22
|
+
|
|
23
|
+
GoTry 把「想去哪」变成「能不能、怎么去」:
|
|
24
|
+
|
|
25
|
+
| 阶段 | AI/Engine | 输什么 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| **动机访谈** | LLM | 必问项:工作窗口 / 已订资源 / 出发城市 |
|
|
28
|
+
| **事实抽取** | LLM | 工作窗口生效 + 休假语义识别 |
|
|
29
|
+
| **可行性判定** | **Z3** | 哪些候选目的地**可行**、哪些**不可行**、为什么、**如何最小化改动让它变可行** |
|
|
30
|
+
| **门到门全成本** | 求解器 | 真实飞行时长(含时差)+ 醒来起夜惩罚 + 接驳代价 + 到达精力 % |
|
|
31
|
+
| **证据链** | 渲染层 | 每个数字标 `[骨架:openflights]`、`[实时API:hbcli]`、`[静态包:估算]` |
|
|
32
|
+
|
|
33
|
+
**不像普通 AI 聊天**——LLM 只做理解和解释,**判定与算术是数学求解器**算的。
|
|
34
|
+
|
|
35
|
+
> **Unlike a regular AI chat**, the LLM only translates and explains. **Decisions and arithmetic are computed by a Z3 solver**, not guessed.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🚀 快速开始 — Quick start
|
|
40
|
+
|
|
41
|
+
### 一行启动(npm,推荐)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx @danceiny/gotry web # 首跑会提示在当前目录建 .env(LLM_API_KEY=<DeepSeek key>)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
装完即得 :3080 对话界面。已装 dsh 的用户同理——上面的命令会自动以 cordis patch
|
|
48
|
+
挂载 gotry 插件,无需任何额外配置(零成本启动)。
|
|
49
|
+
|
|
50
|
+
> rc.5 为不可用首发(已停止指向);`@rc` tag 当前为 rc.7,`latest` 为 rc.9,两者均可跑。rc.10 在仓(双形态冻结 ADR-16),待发——见 release-notes。
|
|
51
|
+
|
|
52
|
+
### 源码安装(开发者)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/Danceiny/gotry
|
|
56
|
+
cd gotry
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### A. 装 vendored dsh runtime(一次性,约 1 分钟)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd ts/dsh-runtime
|
|
63
|
+
pnpm install # 拉 @deepseek-ai/dsh 0.1.1-rc.2 + 依赖(实测 51 秒)
|
|
64
|
+
cd ../..
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> 为什么这一步:dsh runtime 是 vendored 的(`ts/dsh-runtime/`)——
|
|
68
|
+
> 避免 npm 一键分发时把整个 dsh 也打包进去。
|
|
69
|
+
> 之后可以重复使用,无需重装。
|
|
70
|
+
|
|
71
|
+
#### B. 环境前置 — Prerequisites
|
|
72
|
+
|
|
73
|
+
- **Node 22+**(`nvm install 22` 或更高)
|
|
74
|
+
- 一个 LLM API key(默认走 DeepSeek,也支持 OpenAI 兼容协议)
|
|
75
|
+
|
|
76
|
+
#### C. 配 — Configure
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
cp .env.example .env
|
|
80
|
+
# 填 LLM_API_KEY(deepseek: sk-...; 也兼容 OpenAI/Anthropic 兼容协议)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### D. 跑 — Run(仓内入口,开发者)
|
|
84
|
+
|
|
85
|
+
gotry 把 **DeepSeek Harness (dsh) 运行时**作为唯一推荐面——任何自然语言输入在那里面都能跟 GoTry 正常对话(自动调用引擎、追问缺失字段、给出证据链)。
|
|
86
|
+
|
|
87
|
+
启动方式有三种,对应三种使用场景:
|
|
88
|
+
|
|
89
|
+
| 入口 | 命令 | 什么时候用 | 给你什么 |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| **① dsh Web 对话框**(推荐) | `./gotry web` | 第一天上手;持续多轮规划;想看推理过程可视化 | `http://127.0.0.1:3080` 浏览器界面;带 GoTry 人格的对话框 |
|
|
92
|
+
| **② headless 一次性问答** | `./gotry "一句完整任务..."` | 写脚本、CI 自动化、定向 LLM 调试 | stdout 输出 markdown 回答 |
|
|
93
|
+
| **③ help** | `./gotry help` | 不知道用法 | 三行帮助 |
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# 推荐:打开对话框,正常打「我想去大理三天」,看引擎回答
|
|
97
|
+
./gotry web
|
|
98
|
+
# → http://127.0.0.1:3080,在输入框里像跟 DeepSeek 对话一样打字
|
|
99
|
+
|
|
100
|
+
# 调试/自动化:直接一问一答,无浏览器
|
|
101
|
+
./gotry "我想从深圳休整两天,预算 3000"
|
|
102
|
+
# → stdout 拿到引擎判定 + 证据链 + 候选 gate
|
|
103
|
+
|
|
104
|
+
# 不知道用法
|
|
105
|
+
./gotry help
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> ⚠️ **dsh 运行时端口冲突**:如果你之前已经手动 `node ...dsh web` 跑过,而后再 `./gotry web`,会因 `:3080` 占用启动失败。要么先 `kill <PID>`,要么直接用已经在跑的那个。
|
|
109
|
+
>
|
|
110
|
+
> **dsh 启动耗时**:第一次 cold start 6–15 秒(cordis 组合 + plugin 加载 + LLM 准备);之后热启动 < 2 秒。
|
|
111
|
+
>
|
|
112
|
+
> **headless 报错兜底**:意外退出(exit ≠ 0 / signal)会 fsync 一条记录到 `gotry-state/incidents.jsonl`,作为事故证据而不是沉默(D-NEW 进程护栏)。
|
|
113
|
+
|
|
114
|
+
<br>
|
|
115
|
+
|
|
116
|
+
*One-liner mental model: `npx @danceiny/gotry@rc web` + `.env` 一行 key — done。dsh 对话框是唯一产品面(旧 shell 前端已删除)。*
|
|
117
|
+
|
|
118
|
+
> 一行安装的关键点: `./gotry web`(终端粘贴即用),或 `./gotry "任务"`(一句问答)。**任何自然语言都能用**(这是 dsh runtime 投资的最大价值)。详见 [known limitations](#-known-limitations)。
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 🎬 一段对话 — Demo
|
|
124
|
+
|
|
125
|
+
输入 → 引擎判定 → 输出(真实截图,monospace 保留原味):
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
> 我想去洱海边发呆两三天,上海出发,预算 3000,年假别让我办公。
|
|
129
|
+
|
|
130
|
+
GoTry: 收到。先把约束记下来——
|
|
131
|
+
• 窗口: 2 天(2026 年窗口)
|
|
132
|
+
• 出发: 上海
|
|
133
|
+
• 预算: ¥3000 全含
|
|
134
|
+
• 动机: 休整 / [escape_rest: 0.7]
|
|
135
|
+
• 不要求工作窗口(年假)
|
|
136
|
+
• 还没订任何东西
|
|
137
|
+
|
|
138
|
+
引擎判定:
|
|
139
|
+
|
|
140
|
+
**大理·洱海: 现在不行** —— 冲突约束: duration。
|
|
141
|
+
你的 2 天窗口装不下「至少 5 天的洱海休整」(你之前的画像也有此约)。
|
|
142
|
+
放宽方案: 把行程延长到 5 天,约 ¥4950。
|
|
143
|
+
★已放入「下一次出发」清单: 需 5 天起,3-5 / 9-11 月最佳。
|
|
144
|
+
|
|
145
|
+
**千岛湖: 可行** (G7315 06:35 出发,bus 接驳,
|
|
146
|
+
¥996,起床 06:35,到达精力 84%,门到门 5h20m,
|
|
147
|
+
有效休整 4.4h)
|
|
148
|
+
**太湖: 可行** (G101 09:00 出发,
|
|
149
|
+
¥716,起床 07:12,到达精力 84%,
|
|
150
|
+
有效休整 4.6h)
|
|
151
|
+
|
|
152
|
+
建议: 千岛湖(意象匹配 80%)。
|
|
153
|
+
备选: 太湖(¥716,匹配 60%)。
|
|
154
|
+
|
|
155
|
+
**待你决定**:
|
|
156
|
+
1. 千岛湖还是太湖?(前者更贴意象,后者更省)
|
|
157
|
+
2. 出发班次选 G7315(06:35)还是更晚的?
|
|
158
|
+
|
|
159
|
+
[骨架:openflights] ✓ SZX↔PVG 已校验 [实时API:hbcli] 上海机场在跑
|
|
160
|
+
[静态包:估算] G7315/G7316 价格按 7-8 月淡季估算
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> **Brief English summary**: input → engine verdicts (feasibility + whole-cost) → recommendation + wish-pool entry for infeasible candidates. Every numeric carries an evidence tag.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 🏛️ 架构 — Architecture
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
171
|
+
│ L1 对话即界面 chat-as-UI; gates 是消息内选择题 │
|
|
172
|
+
│ L2 编排 dsh 运行时 + GoTry 插件(ReAct);13 个工具 │
|
|
173
|
+
│ L3 领域 统一行程模型 + Z3 可行性引擎(枚举/Z3 双形态) │
|
|
174
|
+
│ L4 数据 静态数据包 + hotelbyte-cli 实时桥 + OpenFlights 骨架 │
|
|
175
|
+
│ L5 治理 LoopX(objective / gates / evidence / quota) │
|
|
176
|
+
└──────────────────────────────────────────────────────────────┘
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
| 层 | 模块 | 角色 |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| L2 | `ts/src/index.ts` (dsh 插件) | 注册 13 个工具,挂 `{{current_date}}`/`{{time_anchor_card}}`/`{{motivation_brief}}` 变量,工具 execute 异常隔离 + 平铺观察 envelope + uncaughtException 护栏 |
|
|
182
|
+
| L3 | `ts/src/unified.ts` · `py/gotry_feasibility/unified.py` | 唯一求解入口(候选枚举 + 航班链 Z3) |
|
|
183
|
+
| L4 | `ts/capabilities/hbcli.ts` | hotelbyte-cli 进程封装 + 降级到 `data/hotels_2026.json` |
|
|
184
|
+
| L4 | `ts/scripts/skeleton-check.ts` | OpenFlights 168 对枢纽,三值语义(阳性/枢纽对否定≠证伪/枢纽外=无结论) |
|
|
185
|
+
|
|
186
|
+
> 📖 完整 ADR / 演进阶段 / 债务清单见 [`docs/architecture.md`](docs/architecture.md)。
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## ⚠️ Known limitations · 已知限制
|
|
192
|
+
|
|
193
|
+
- ✅ **License MIT** (D-1 落地 2026-08-23)
|
|
194
|
+
- **zh-CN 体验** — 当前面向中国出境首发(你的账号语言习惯)。英文界面/wider 国际化未做,等 M4 校准输入落定。
|
|
195
|
+
- **机票实时数据** — 静态包(`data/flights_2026.json`)作为降级。实时票价接入留到 v0.1.x。
|
|
196
|
+
- **Z3 WASM race** — 连续跑多个测试套件时偶发 `memory access out of bounds`(已规避回滚)。M3 早期处理。
|
|
197
|
+
- **薄壳已废弃** — `./gotry shell` 不再推荐;dsh web 是唯一面。本版 README 仍保留旧命令行做迁移证据。
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 📚 文档 — Docs
|
|
203
|
+
|
|
204
|
+
| 文档 | 适合谁 |
|
|
205
|
+
|---|---|
|
|
206
|
+
| [本 README](README.md) | **所有人**:是什么 · 怎么用 · 限制 |
|
|
207
|
+
| [`docs/architecture.md`](docs/architecture.md) | 工程师:分层 · ADR · 演进 · 债务 |
|
|
208
|
+
| [`docs/roadmap.md`](docs/roadmap.md) | 项目管理:M0–M6 与当前位置 |
|
|
209
|
+
| [`docs/user-guide.md`](docs/user-guide.md) | 终端用户:详细使用文档 |
|
|
210
|
+
| [`docs/gotry-product-design.md`](docs/gotry-product-design.md) | 产品:主循环 · 透明机制 · 全成本模型 |
|
|
211
|
+
| [`docs/gotry-master-outline.md`](docs/gotry-master-outline.md) | 决策者:工作分解 · 复用矩阵 |
|
|
212
|
+
| [`docs/kimi-postmortem.md`](docs/kimi-postmortem.md) | 所有人:真实 AI 旅行规划失败复盘(反面教材) |
|
|
213
|
+
| [`docs/release-notes.md`](docs/release-notes.md) | 历史:每个 tag 的发布闸勾稽 |
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 🧪 跑测试 — Verify
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
./scripts/run-all-tests.sh
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
全栈一次性绿(纯 TS,无 Python 依赖):engine/journey/unified 金标准 · 对话重放 ·
|
|
225
|
+
异步工单跨进程 · 插件 smoke · hbcli · 进程护栏(含工具异常隔离) · 天气 · 航班 ·
|
|
226
|
+
Anything · probePoi · agent-reach(web/deep/wrapper)· 双路径稳定性 ·
|
|
227
|
+
时间感评测(锚点卡/槽位过期校验/评分器/mock 回放;真模型巡检 `time-eval-tests.ts --real`)·
|
|
228
|
+
记忆域(动机合并守门/效用 sidecar/只读指标投影)。
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 🤝 参与开发 — Contributing
|
|
233
|
+
|
|
234
|
+
仓库内 branch 模型:`main` 是唯一分支(没有 dev/staging)。所有迭代在 main 直接推(每次提交需全栈绿)。
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
./scripts/run-all-tests.sh # 提交前必跑
|
|
238
|
+
|
|
239
|
+
# 单独跑某个套件
|
|
240
|
+
cd ts && npx tsx scripts/replay.ts # 对话重放(mock,不需要 key)
|
|
241
|
+
cd ts && npx tsx scripts/hbcli-tests.ts # 能力封装
|
|
242
|
+
cd ts && npx tsx scripts/incident-tests.ts # 进程护栏
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
ADR 与技术债见 [`docs/architecture.md` §8 / §10](docs/architecture.md)。Agent 协作契约见 [`AGENTS.md`](AGENTS.md)。
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 📜 License
|
|
250
|
+
|
|
251
|
+
**MIT**(2026-08-23 落定)。与上游 dsh(MIT)/ loopx 一致——宽松、可商用、可闭源分叉。
|
|
252
|
+
|
|
253
|
+
文本见 [LICENSE](LICENSE)。
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 🌐 中英版 — Locales
|
|
258
|
+
|
|
259
|
+
- **English section summaries** are inline above (italic blockquotes).
|
|
260
|
+
- 本 README 主语言是中文,面向中国出境首发种子用户群。
|
|
261
|
+
- 完整英文版 README 计划在 v0.1.0 同步([issue 路线](#-known-limitations))。
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
**Built with**: DeepSeek Harness 0.1.1-rc.2 · Cordis · Z3 (WASM) · loopx (pipx) · hotelbyte-cli · Agent-Reach v1.5.0 (`.venv/`) · OpenFlights · TypeScript · Bun
|
|
266
|
+
|
|
267
|
+
**Last verified against `v0.0.1-rc.9`(已发布,2026-08-28)** — 全栈回归全绿(发布流程见 `scripts/publish-npm.sh`,内部决策见 [docs/decisions-needed.md](docs/decisions-needed.md)、[docs/tokens.md](docs/tokens.md))。
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gotry CLI 入口(v0.0.1-rc.2 起;rc.6 起支持 npm 安装运行):
|
|
4
|
+
* gotry web # dsh Web 浏览器界面(:3080)
|
|
5
|
+
* gotry "I want 2 days in Phuket" # headless one-shot
|
|
6
|
+
* gotry help # help
|
|
7
|
+
*
|
|
8
|
+
* 工作流程:
|
|
9
|
+
* 1. 解析 argv + .env(provider-neutral → DEEPSEEK_API_KEY)
|
|
10
|
+
* 2. 定位 dsh runtime:
|
|
11
|
+
* a. repo checkout → vendored ts/dsh-runtime/node_modules/...
|
|
12
|
+
* b. npm 安装 → createRequire 解析依赖 @deepseek-ai/dsh(不走 npx:
|
|
13
|
+
* dsh cordis-loader 在子 cwd 求值 plugin name,必须绝对路径 patch)
|
|
14
|
+
* 3. 运行时生成 patch(os.tmpdir):把 gotry-tools 插件路径重写为按本包
|
|
15
|
+
* 位置解析的绝对路径 —— 仓内 cordis.gotry-patch.yml 里的 name 行只是
|
|
16
|
+
* 占位(本机绝对路径),随 tarball 分发后对其他机器必错。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawn } from 'node:child_process'
|
|
20
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
21
|
+
import { fileURLToPath } from 'node:url'
|
|
22
|
+
import { dirname, join } from 'node:path'
|
|
23
|
+
import { tmpdir } from 'node:os'
|
|
24
|
+
import { createRequire } from 'node:module'
|
|
25
|
+
|
|
26
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
27
|
+
const repoRoot = join(here, '..')
|
|
28
|
+
const require_ = createRequire(import.meta.url)
|
|
29
|
+
|
|
30
|
+
// --- 环境 .env 加载(provider-neutral) ---
|
|
31
|
+
// npm 安装模式优先读用户当前目录的 .env(包目录内不该有凭证);repo 检出读仓根。
|
|
32
|
+
const vendoredDshEarly = existsSync(join(repoRoot, 'ts/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js'))
|
|
33
|
+
const envCandidates = vendoredDshEarly
|
|
34
|
+
? [join(repoRoot, '.env')]
|
|
35
|
+
: [join(process.cwd(), '.env'), join(repoRoot, '.env')]
|
|
36
|
+
const envLines = []
|
|
37
|
+
for (const p of envCandidates) {
|
|
38
|
+
if (existsSync(p)) envLines.push(...readFileSync(p, 'utf-8').split('\n'))
|
|
39
|
+
}
|
|
40
|
+
for (const line of envLines) {
|
|
41
|
+
const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/i)
|
|
42
|
+
if (!m || line.trim().startsWith('#')) continue
|
|
43
|
+
const k = m[1]; const v = m[2].trim().replace(/^["']|["']$/g, '')
|
|
44
|
+
if (process.env[k] === undefined) process.env[k] = v
|
|
45
|
+
}
|
|
46
|
+
if (process.env.LLM_API_KEY && !process.env.DEEPSEEK_API_KEY) {
|
|
47
|
+
process.env.DEEPSEEK_API_KEY = process.env.LLM_API_KEY
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- argv 解析 ---
|
|
51
|
+
const args = process.argv.slice(2)
|
|
52
|
+
const help = args.length === 0 || args[0] === '-h' || args[0] === '--help' || args[0] === 'help'
|
|
53
|
+
|
|
54
|
+
if (help) {
|
|
55
|
+
console.log(`GoTry — dsh-driven AI travel agent
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
gotry web # dsh Web UI on http://127.0.0.1:3080
|
|
59
|
+
gotry "一段完整任务..." # headless 一问一答
|
|
60
|
+
gotry help # this help
|
|
61
|
+
|
|
62
|
+
Prerequisites:
|
|
63
|
+
• Node 22+
|
|
64
|
+
• \`.env\` 里 LLM_API_KEY (DeepSeek / OpenAI 兼容均可)
|
|
65
|
+
|
|
66
|
+
Detail: https://github.com/Danceiny/gotry — README
|
|
67
|
+
`)
|
|
68
|
+
process.exit(0)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// mode 决定路径: 'web'/'help'/'help' 是字面命令;否则第一段 args[0] 是任务本身的一部分
|
|
72
|
+
const literal = new Set(['web', 'help', '-h', '--help'])
|
|
73
|
+
const isLiteral = literal.has(args[0])
|
|
74
|
+
const mode = isLiteral ? args[0] : 'headless'
|
|
75
|
+
const rest = isLiteral ? args.slice(1) : args
|
|
76
|
+
|
|
77
|
+
// --- dsh runtime 定位:repo checkout(vendored)优先,npm 安装走依赖解析 ---
|
|
78
|
+
const vendoredDsh = join(repoRoot, 'ts/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js')
|
|
79
|
+
let dshBin = ''
|
|
80
|
+
let dshCwd = ''
|
|
81
|
+
if (existsSync(vendoredDsh)) {
|
|
82
|
+
dshBin = vendoredDsh
|
|
83
|
+
dshCwd = join(repoRoot, 'ts/dsh-runtime')
|
|
84
|
+
} else {
|
|
85
|
+
try {
|
|
86
|
+
dshBin = require_.resolve('@deepseek-ai/dsh/lib/bin.js')
|
|
87
|
+
dshCwd = process.cwd() // npm 安装:gotry-state 落在用户调用目录
|
|
88
|
+
} catch {
|
|
89
|
+
dshBin = ''
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!dshBin) {
|
|
94
|
+
console.error(`[gotry] 找不到 dsh runtime(既无 vendored ${vendoredDsh},依赖里也没有 @deepseek-ai/dsh)。`)
|
|
95
|
+
console.error('repo checkout: cd ts/dsh-runtime && pnpm install;npm 安装: npm install(检查 node_modules)。')
|
|
96
|
+
process.exit(1)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// --- 运行时 patch:插件路径按本包位置重写为绝对路径 ---
|
|
100
|
+
// npm 模式指向 dist/ 纯 JS(Node 拒绝 strip node_modules 下的 .ts);
|
|
101
|
+
// repo 检出指向 .ts 源码(vendored runtime 下天然可行)。
|
|
102
|
+
const distEntry = join(repoRoot, 'dist/src/index.js')
|
|
103
|
+
const tsEntry = join(repoRoot, 'ts/src/index.ts')
|
|
104
|
+
const npmMode = !existsSync(vendoredDsh)
|
|
105
|
+
const pluginEntry = npmMode && existsSync(distEntry) ? distEntry : tsEntry
|
|
106
|
+
const staticPatch = join(repoRoot, 'cordis.gotry-patch.yml')
|
|
107
|
+
if (!existsSync(staticPatch)) {
|
|
108
|
+
console.error(`[gotry] 找不到 patch 配置: ${staticPatch}`)
|
|
109
|
+
process.exit(1)
|
|
110
|
+
}
|
|
111
|
+
if (!existsSync(pluginEntry)) {
|
|
112
|
+
console.error(`[gotry] 找不到插件入口: ${pluginEntry}(包不完整?)`)
|
|
113
|
+
process.exit(1)
|
|
114
|
+
}
|
|
115
|
+
// dsh-map-tools 宿主插件(地图/路线/POI,零 key 走 OSM/OSRM):repo 用 vendored,
|
|
116
|
+
// npm 用依赖解析;都找不到就整块剔除 patch 条目(缺地图不挡旅行规划)
|
|
117
|
+
let mapEntry = ''
|
|
118
|
+
const vendoredMap = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-map-tools/lib/index.js')
|
|
119
|
+
if (existsSync(vendoredMap)) {
|
|
120
|
+
mapEntry = vendoredMap
|
|
121
|
+
} else {
|
|
122
|
+
// npm 布局:子路径可能被 exports 挡(resolve 抛错不能留下旧值),裸包名返回真实入口
|
|
123
|
+
try { mapEntry = require_.resolve('dsh-map-tools/lib/index.js') } catch { mapEntry = '' }
|
|
124
|
+
if (!mapEntry) { try { mapEntry = require_.resolve('dsh-map-tools') } catch { mapEntry = '' } }
|
|
125
|
+
}
|
|
126
|
+
// 结构化澄清卡(T2):ask_user_question 工具 + user-questions 服务,从 dsh 包
|
|
127
|
+
// 上下文解析(pnpm 嵌套布局下只有 dsh 自己看得见这些依赖);失败整块剔除
|
|
128
|
+
// 澄清卡注入(T2):web 用 dsh 原生卡片;headless+TTY 用 gotry 的 stdio 提供方
|
|
129
|
+
// (终端渲染选择题);headless 非 TTY(CI/管道)不注入——工具收到 NO_PROVIDER
|
|
130
|
+
// 错误,人格契约 (5) 退化文本。GOTRY_ASK_STDIO=1 强制启用(测试/外接答复)。
|
|
131
|
+
const stdioAsk = mode === 'headless' && (process.stdin.isTTY || process.env.GOTRY_ASK_STDIO === '1')
|
|
132
|
+
? join(here, 'gotry-stdio-ask.js') : ''
|
|
133
|
+
let askUserInsert = ''
|
|
134
|
+
if (mode === 'web' || stdioAsk) try {
|
|
135
|
+
const reqFromDsh = createRequire(dshBin)
|
|
136
|
+
// 裸包名解析(这些包的 exports 不暴露 ./lib/index.js 子路径,但裸名返回真实入口)
|
|
137
|
+
// userQuestions 服务默认树已注册(重复插入会崩),只插工具消费者
|
|
138
|
+
const at = reqFromDsh.resolve('@deepseek-ai/dsh-tool-ask-user')
|
|
139
|
+
askUserInsert = (stdioAsk ? ` - id: gotry-stdio-ask\n name: '${stdioAsk}'\n` : '')
|
|
140
|
+
+ ` - id: dsh-tool-ask-user\n name: '${at}'`
|
|
141
|
+
} catch { /* 缺件不挡启动 */ }
|
|
142
|
+
|
|
143
|
+
let patchRaw = readFileSync(staticPatch, 'utf-8')
|
|
144
|
+
.replace(/(name:\s*)'[^']*ts\/src\/index\.ts'/, `$1'${pluginEntry}'`)
|
|
145
|
+
.replace(/^\s*# \{ask-user-insert\}.*\n(\s*# .*\n)?/m, askUserInsert ? askUserInsert + '\n' : '')
|
|
146
|
+
if (mapEntry) {
|
|
147
|
+
patchRaw = patchRaw.replace(/(name:\s*)'placeholder\/dsh-map-tools'/, `$1'${mapEntry}'`)
|
|
148
|
+
} else {
|
|
149
|
+
patchRaw = patchRaw.replace(/\n\s*- id: dsh-map-tools\n\s*name: 'placeholder\/dsh-map-tools'\n/, '\n')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// dsh-calendar 宿主插件(CalDAV 工作窗口读取;未配置时工具报错降级,不挡启动)
|
|
153
|
+
let calEntry = ''
|
|
154
|
+
const vendoredCal = join(repoRoot, 'ts/dsh-runtime/node_modules/dsh-calendar/lib/index.js')
|
|
155
|
+
if (existsSync(vendoredCal)) {
|
|
156
|
+
calEntry = vendoredCal
|
|
157
|
+
} else {
|
|
158
|
+
try { calEntry = require_.resolve('dsh-calendar/lib/index.js') } catch { calEntry = '' }
|
|
159
|
+
if (!calEntry) { try { calEntry = require_.resolve('dsh-calendar') } catch { calEntry = '' } }
|
|
160
|
+
}
|
|
161
|
+
if (calEntry) {
|
|
162
|
+
patchRaw = patchRaw.replace(/(name:\s*)'placeholder\/dsh-calendar'/, `$1'${calEntry}'`)
|
|
163
|
+
} else {
|
|
164
|
+
patchRaw = patchRaw.replace(/\n\s*- id: dsh-calendar\n\s*name: 'placeholder\/dsh-calendar'\n/, '\n')
|
|
165
|
+
}
|
|
166
|
+
const patchPath = join(tmpdir(), `cordis.gotry.${process.pid}.yml`)
|
|
167
|
+
writeFileSync(patchPath, patchRaw)
|
|
168
|
+
if (!process.env.DEEPSEEK_API_KEY && mode !== 'help') {
|
|
169
|
+
console.error('[gotry] 缺少 LLM API key —— 两种方式任选其一后重跑:')
|
|
170
|
+
console.error(` 1) 在当前目录创建 .env 写入一行: LLM_API_KEY=<你的 DeepSeek key>(key 从 https://platform.deepseek.com 获取)`)
|
|
171
|
+
console.error(' 2) 或临时环境变量: export LLM_API_KEY=<key>')
|
|
172
|
+
process.exit(1)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// --- 调 vendored dsh 二进制(不走 npx)---
|
|
176
|
+
// headless: 第一个非 -- 之后的参数是 task —— gotry argv 0 是 headless 触发,其余都算 task;
|
|
177
|
+
const binJs = mode === 'web'
|
|
178
|
+
? ['web', '--patch', patchPath, ...(process.argv.includes('--no-open') ? ['--no-open'] : [])]
|
|
179
|
+
: ['--profile', 'headless', '--patch', patchPath, ...rest]
|
|
180
|
+
|
|
181
|
+
const child = spawn(process.execPath, [dshBin, ...binJs], {
|
|
182
|
+
stdio: 'inherit',
|
|
183
|
+
env: process.env,
|
|
184
|
+
cwd: dshCwd,
|
|
185
|
+
})
|
|
186
|
+
if (process.env.GOTRY_DEBUG) {
|
|
187
|
+
console.error('[gotry-debug] exec:', process.execPath, dshBin)
|
|
188
|
+
console.error('[gotry-debug] argv:', binJs)
|
|
189
|
+
console.error('[gotry-debug] cwd:', dshCwd)
|
|
190
|
+
console.error('[gotry-debug] patch exists:', existsSync(patchPath), patchPath)
|
|
191
|
+
}
|
|
192
|
+
child.on('exit', (code, signal) => {
|
|
193
|
+
if (code !== 0 || signal) {
|
|
194
|
+
// D-NEW 护栏:dsh 异常退出也写一条 incident,留现场而非沉默
|
|
195
|
+
// npm 模式 import dist JS(node_modules 下的 .ts 会被 Node 拒 strip)
|
|
196
|
+
const incidentModule = npmMode && existsSync(join(repoRoot, 'dist/capabilities/incident-log.js'))
|
|
197
|
+
? '../dist/capabilities/incident-log.js'
|
|
198
|
+
: '../ts/capabilities/incident-log.ts'
|
|
199
|
+
import(incidentModule).then(({ recordIncident }) => {
|
|
200
|
+
recordIncident({
|
|
201
|
+
ts: new Date().toISOString(),
|
|
202
|
+
kind: 'plugin_error',
|
|
203
|
+
message: `gotry spawn exit: ${mode} code=${code} signal=${signal}`,
|
|
204
|
+
source: 'gotry-cli',
|
|
205
|
+
}, repoRoot).catch(() => {})
|
|
206
|
+
}).catch(() => {}) // module 找不到时静默
|
|
207
|
+
}
|
|
208
|
+
process.exit(code ?? 1)
|
|
209
|
+
})
|
|
210
|
+
child.on('error', (e) => {
|
|
211
|
+
console.error(`[gotry] dsh 启动失败: ${e.message}`)
|
|
212
|
+
console.error('尝试: node -v 看 Node 版本(需 22+),或查看 ts/dsh-runtime/node_modules/。')
|
|
213
|
+
process.exit(1)
|
|
214
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gotry stdio 澄清卡提供方(T2 补强,ask_user_question 的 headless/TTY 形态):
|
|
3
|
+
*
|
|
4
|
+
* dsh 的 userQuestions 服务是「单一 UI 提供方」架构——web 界面注册一种,这里注册
|
|
5
|
+
* 终端形态:ask_user_question 在交互终端渲染成选择题,读一行序号作答复。业界对照
|
|
6
|
+
* (Claude Code -p 最终做法是 headless 直接不暴露工具;MCP elicitation 规范:无提供
|
|
7
|
+
* 方应作为错误/优雅降级传回模型)——我们比照实现为:
|
|
8
|
+
* - headless + TTY(人坐在终端前):注册本插件,终端问答(优于纯文本退化)
|
|
9
|
+
* - headless + 非 TTY(CI/管道):不注册 → 工具收到 NO_PROVIDER 错误 → 人格契约
|
|
10
|
+
* (5) 退化文本选择题;GOTRY_ASK_STDIO=1 可强制启用(测试/外接答复管道)
|
|
11
|
+
*
|
|
12
|
+
* 插件形态遵循 dsh 约定(name/inject/apply);纯 JS 无需 dist 编译。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createInterface } from 'node:readline'
|
|
16
|
+
|
|
17
|
+
export const name = 'gotry-stdio-ask'
|
|
18
|
+
export const inject = ['userQuestions']
|
|
19
|
+
|
|
20
|
+
export function apply(ctx) {
|
|
21
|
+
const svc = ctx.userQuestions
|
|
22
|
+
if (!svc || typeof svc.registerProvider !== 'function') return
|
|
23
|
+
|
|
24
|
+
svc.registerProvider({
|
|
25
|
+
async ask(request) {
|
|
26
|
+
const answers = []
|
|
27
|
+
for (const q of request.questions ?? []) {
|
|
28
|
+
if (request.signal?.aborted) { answers.push({ id: q.id, selected: [] }); continue }
|
|
29
|
+
const opts = q.options ?? []
|
|
30
|
+
const lines = ['', `❓ ${q.header ? `${q.header} — ` : ''}${q.question}`]
|
|
31
|
+
opts.forEach((o, i) => lines.push(` ${i + 1}. ${o.label}${o.detail ? ` — ${o.detail}` : ''}`))
|
|
32
|
+
lines.push(q.multiSelect ? ' (多选:序号逗号分隔;回车=跳过,可输自定义文本)' : ' (输入序号回车;回车=跳过,可输自定义文本)')
|
|
33
|
+
process.stdout.write(lines.join('\n') + '\n> ')
|
|
34
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, signal: request.signal })
|
|
35
|
+
let raw = ''
|
|
36
|
+
try {
|
|
37
|
+
raw = (await new Promise((resolve) => {
|
|
38
|
+
rl.once('line', resolve)
|
|
39
|
+
rl.once('close', () => resolve(''))
|
|
40
|
+
if (request.signal) request.signal.addEventListener('abort', () => { try { rl.close() } catch { /* ignore */ } }, { once: true })
|
|
41
|
+
})).trim()
|
|
42
|
+
} finally { try { rl.close() } catch { /* ignore */ } }
|
|
43
|
+
const selected = []
|
|
44
|
+
let custom
|
|
45
|
+
if (raw && /^[0-9,\s]+$/.test(raw)) {
|
|
46
|
+
for (const part of raw.split(/[,\s]+/).filter(Boolean)) {
|
|
47
|
+
const o = opts[Number(part) - 1]
|
|
48
|
+
if (o) selected.push(o.label)
|
|
49
|
+
}
|
|
50
|
+
} else if (raw) {
|
|
51
|
+
custom = raw
|
|
52
|
+
}
|
|
53
|
+
// 空输入=跳过(MCP elicitation 规范:decline 是正常答复,不是错误)
|
|
54
|
+
answers.push({ id: q.id, selected, ...(custom !== undefined ? { custom } : {}) })
|
|
55
|
+
}
|
|
56
|
+
return { answers }
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
}
|
package/bin/gotry.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/gotry.js: npm bin entry(sh wrapper,找 node 路径,exec 真正代码 bin/gotry-inner.js)
|
|
3
|
+
# 解决 nvm 没 source 时 `env: node: not found`。
|
|
4
|
+
# 优先:$PATH → nvm 常见路径 → 系统路径 → 找不到给一次性提示。
|
|
5
|
+
# 零新依赖;不改 npm bin 名(强制 .js 后缀),实际 node 代码在 bin/gotry-inner.js。
|
|
6
|
+
|
|
7
|
+
conf="$(command -v node 2>/dev/null)"
|
|
8
|
+
if [ -z "$conf" ] || [ ! -x "$conf" ]; then
|
|
9
|
+
for cand in \
|
|
10
|
+
"$HOME/.nvm/versions/node/v24.16.0/bin/node" \
|
|
11
|
+
"$HOME/.nvm/versions/node/v22.16.0/bin/node" \
|
|
12
|
+
/usr/local/bin/node \
|
|
13
|
+
/opt/homebrew/bin/node
|
|
14
|
+
do
|
|
15
|
+
if [ -x "$cand" ]; then conf="$cand"; break; fi
|
|
16
|
+
done
|
|
17
|
+
fi
|
|
18
|
+
if [ -z "$conf" ] || [ ! -x "$conf" ]; then
|
|
19
|
+
cat >&2 <<'EOF'
|
|
20
|
+
gotry: node not found in PATH. Install Node 22+:
|
|
21
|
+
macOS: brew install node@22 (or use nvm)
|
|
22
|
+
Linux: https://nodejs.org/
|
|
23
|
+
|
|
24
|
+
Then re-run: gotry web
|
|
25
|
+
EOF
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
exec "$conf" "$(dirname "$0")/gotry-inner.js" "$@"
|