@danceiny/gotry 0.0.1-rc.14 → 0.0.1-rc.15

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
@@ -46,7 +46,7 @@ GoTry turns "I want to go somewhere" into "can I, how, and at what true cost":
46
46
  | **Fact extraction** | LLM | Working hours semantics, leave semantics |
47
47
  | **Feasibility verdict** | **Z3 solver** | Which destinations are feasible / infeasible, why, and the **smallest change that makes them feasible** |
48
48
  | **Door-to-door true cost** | Solver | Real flight duration (incl. time zones) + early-wake penalty + transfer cost + arrival energy % |
49
- | **Evidence chain** | Render layer | Every number tagged: `[骨架:openflights]` / `[实时API:flyai]` / `[静态包:估算]` |
49
+ | **Evidence chain** | Render layer | Every number carries a **source tag**: `[骨架:openflights]` = route existence verified against the public route database; `[实时API:flyai]` = pulled live from an API seconds ago; `[静态包:估算]` = a researched estimate (**not realtime — verify before booking**). On degradation the tag switches honestly — an estimate never poses as realtime |
50
50
 
51
51
  **Unlike a regular AI chat**, the LLM only translates and explains. **Decisions and arithmetic are computed by a Z3 solver**, not guessed.
52
52
 
@@ -78,7 +78,7 @@ cp .env.example .env # ② set LLM_API_KEY
78
78
 
79
79
  ---
80
80
 
81
- ## 🧰 18 tools
81
+ ## 🧰 20 tools
82
82
 
83
83
  | Group | Tool | What it does |
84
84
  |---|---|---|
@@ -94,6 +94,7 @@ cp .env.example .env # ② set LLM_API_KEY
94
94
  | **Memory & reachability** | `gotry_motivation_save` | Persist motivation profile (evidence mandatory, anti-fabrication) |
95
95
  | | `gotry_wish_pool_add` / `gotry_wish_pool_list` | "next departure" wish pool + 0..1 conditional recall |
96
96
  | | `gotry_companion_save` · `gotry_trip_log` | companion profile / travel timeline |
97
+ | **Artifacts** | `gotry_artifacts_list` / `gotry_artifacts_read` | Discover & view generated artifacts (async deliverables + working-dir markdown) as a line-numbered file view (read-only) |
97
98
  | **General external** | `gotry_web_search` · `gotry_video_subtitle` · `gotry_github_search` · `gotry_agent_reach` | web / subtitles / GitHub / all-channel external info (via Agent-Reach) |
98
99
 
99
100
  ---
@@ -131,6 +132,8 @@ Engine verdict:
131
132
  [static-pack:estimate] G7315/G7316 priced on Jul–Aug off-season rates
132
133
  ```
133
134
 
135
+ > Tag guide: `[骨架:openflights]` means "this route can be flown" was verified against the public route database; `[实时API:*]` marks data pulled live seconds ago; `[静态包:估算]` flags an off-season estimate — **verify before booking**. Tags are attached by the render layer, never by the model.
136
+
134
137
  ---
135
138
 
136
139
  ## 🏛️ Architecture
@@ -138,7 +141,7 @@ Engine verdict:
138
141
  ```
139
142
  ┌──────────────────────────────────────────────────────────────┐
140
143
  │ L1 chat-as-interface; gates are in-message choice cards │
141
- │ L2 orchestration dsh runtime + GoTry plugin (ReAct); 18 tools│
144
+ │ L2 orchestration dsh runtime + GoTry plugin (ReAct); 20 tools│
142
145
  │ L3 domain unified itinerary model + Z3 feasibility engine │
143
146
  │ L4 data static packs + hotelbyte-cli bridge + OpenFlights │
144
147
  │ L5 governance LoopX (objective / gates / evidence / quota) │
@@ -147,9 +150,9 @@ Engine verdict:
147
150
 
148
151
  | Layer | Module | Role |
149
152
  |---|---|---|
150
- | L2 | `ts/src/index.ts` (dsh plugin) | 18 tools, time-anchor & memory-brief variables; execute isolation + consent gate + process guards |
153
+ | L2 | `ts/src/index.ts` (dsh plugin) | 20 tools, time-anchor & memory-brief variables; execute isolation + consent gate + process guards |
151
154
  | L3 | `ts/src/unified.ts` · `py/gotry_feasibility/` | single solving entry (candidate enumeration + flight-chain Z3) |
152
- | L4 | `ts/capabilities/hbcli.ts` · `skeleton-check.ts` | realtime inventory bridge + OpenFlights skeleton (three-valued semantics) |
155
+ | L4 | `ts/capabilities/effect.ts` · `hbcli.ts` · `skeleton-check.ts` | effect interpreter (backoff retry / circuit breaker / mock interpreter, issue #16) + realtime inventory bridge + OpenFlights skeleton (three-valued semantics) |
153
156
  | L5 | loopx governance | objective / gates / evidence / quota |
154
157
 
155
158
  > 📖 Full ADRs / evolution / debt ledger: [`docs/architecture.md`](docs/architecture.md) (Chinese — English versions planned for v0.1.0)
@@ -158,24 +161,23 @@ Engine verdict:
158
161
 
159
162
  ## ⚠️ Status & limitations
160
163
 
161
- **Ready (green, verifiable):**
164
+ **Working today** (full-stack regression §1–§34 green; every item has deterministic tests):
162
165
 
163
- - **Z3 WASM race fixed** (`z3-shared.ts` single instance + session-level mutex; run-all §30 concurrency regression gate)
164
- - **Realtime flight/hotel/session retrieval** via the FlyAI official channel (flight/train/hotel; `GOTRY_REALTIME_PRICING=1` optionally overwrites priced legs in the solver)
165
- - **English output for the deterministic solve layer** (`GOTRY_LOCALE=en`, zero missing keys)
166
- - **Account session: consent gate + productized login + auto-detection** (see [🔐 Account session](#-account-session-consent--privacy))
167
- - License MIT; legacy shell frontend removed (dsh web is the only product surface)
166
+ - **Z3 solving engine** feasibility verdicts + door-to-door whole-cost; the historical concurrency race is fixed (§30 regression gate)
167
+ - **Realtime retrieval** flight/train/hotel (Fliggy official channel), destination/hotel catalogs, weather, live flight observation, route connectivity; realtime prices can overwrite solver prices (`GOTRY_REALTIME_PRICING=1`)
168
+ - **Account session search** Ctrip flights on your own logged-in Chrome; consent & privacy rules above (see 🔐 **Account session: consent & privacy**)
169
+ - **Memory & reachability** motivation profile / wish pool / companions / travel timeline; English output via `GOTRY_LOCALE=en`
168
170
 
169
- **Open (honest list):**
171
+ **Open limitations** (as of 2026-08-29, honest list):
170
172
 
171
- - ⏳ **M3 Exit not closed** — engineering & distribution ready, but real seed-user evidence (50–200 person cohort) not yet accumulated; synthetic fixtures prove contracts, not business pass
172
- - ⏳ **Non-Chinese UI remnants** — the dsh web UI belongs to the host; tool result cards and persona dialogue localization await M4 calibration samples
173
- - ⏳ **Ctrip-hotel / Meituan session adapters** flight done; hotel session surfaces await real login-state backfill (next tick)
173
+ - ⏳ **M3 Exit not closed** — engineering & distribution ready, but real seed-user evidence (50–200 person cohort) not yet accumulated; automated tests prove contracts and formulas, not business pass
174
+ - ⏳ **Ctrip-hotel / Meituan logged-in adapters** — flights done; hotel session surfaces await real login-state backfill (next tick)
175
+ - ⏳ **Interface language** English currently covers the deterministic solve-output layer only; the dsh host UI and dialogue surface belong to the host / calibration samples
174
176
 
175
177
  <details>
176
- <summary>📖 Full status ledger (transactional state / evidence contracts / milestone stance)</summary>
178
+ <summary>📖 Deeper engineering state (ledger contracts / evidence contracts / milestone stance)</summary>
177
179
 
178
- Transactional state ledger (ADR-15, `gotry_async_terminal.v1`: 4/4→`succeeded`/ledger `settled`/exit 0; non-4/4→`failed`/`failed`/exit 2; replaying the terminal state recomputes nothing); dual-form architecture freeze (ADR-16: one ledger semantics for local+Web, tenant_id first-class); session-data-plane #21 field fixture scorer, dual-source contract and waiting-attach no-spend are in deterministic regression, real sf-01..08 not yet accepted; milestone stance (2026-08-29): M3 engineering & distribution ready but real seed-user evidence not closed, M4 proceeds under founder authorization without constituting M3 Exit proof, M5/M6 only after their Entry gates details in [`docs/roadmap.md`](docs/roadmap.md) and [`docs/architecture.md`](docs/architecture.md).
180
+ The authoritative state lives in the docs, not this README: transactional state ledger (ADR-15) + dual-form freeze (ADR-16: one ledger semantics for local+web); the M3 real-cohort evidence contract stands (fixtures don't count toward Exit; 50–200 real samples open the gate); the M4 paired-cohort value evidence contract (run-all §34 synthetic data is never Exit evidence); async work-order terminal contract (`gotry_async_terminal.v1`: 4/4 succeeded / ledger settled / exit 0). Details: [`docs/roadmap.md`](docs/roadmap.md) / [`docs/architecture.md`](docs/architecture.md) §1 and issues #19–#22.
179
181
 
180
182
  </details>
181
183
 
@@ -187,7 +189,7 @@ Transactional state ledger (ADR-15, `gotry_async_terminal.v1`: 4/4→`succeeded`
187
189
  ./scripts/run-all-tests.sh
188
190
  ```
189
191
 
190
- One-shot full-stack green (pure TS, no Python needed): golden engines · dialogue replay · cross-process async work-orders · plugin smoke · hbcli · process guards · weather · flights · Anything · probePoi · agent-reach · dual-path stability · time-awareness eval · memory domain · **Z3 race (§30) · realtime pricing (§31) · i18n catalog (§32) · M3 cohort evidence contract (§33) · M4 value evidence contract (§34)**.
192
+ One-shot full-stack green (pure TS, no Python needed): golden engines · dialogue replay · cross-process async work-orders · plugin smoke · hbcli · process guards · weather · flights · Anything · probePoi · agent-reach · dual-path stability · time-awareness eval · memory domain · **Z3 race (§30) · realtime pricing (§31) · i18n catalog (§32) · M3 cohort evidence contract (§33) · M4 value evidence contract (§34) · M3 nightly evidence producer contract (§35)**.
191
193
 
192
194
  ---
193
195
 
@@ -207,4 +209,4 @@ Standard open-source flow: branch off latest `main` (`feat/ · fix/ · docs/ ·
207
209
 
208
210
  **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
209
211
 
210
- **Last verified against `v0.0.1-rc.14` (2026-08-29)** — full-stack regression green §1-§34 (release flow: `scripts/publish-npm.sh`).
212
+ **Last verified against `v0.0.1-rc.15` (2026-08-29)** — full-stack regression green §1-§36 (release flow: `scripts/publish-npm.sh`).
package/README.zh-CN.md CHANGED
@@ -62,7 +62,7 @@ GoTry 把「想去哪」变成「能不能、怎么去」:
62
62
  | **事实抽取** | LLM | 工作窗口生效 + 休假语义识别 |
63
63
  | **可行性判定** | **Z3 求解器** | 哪些候选可行、哪些不可行、为什么、**最小改动让它可行** |
64
64
  | **门到门全成本** | 求解器 | 真实飞行时长(含时差)+ 醒来起夜惩罚 + 接驳代价 + 到达精力 % |
65
- | **证据链** | 渲染层 | 每个数字带标签:`[骨架:openflights]` / `[实时API:flyai]` / `[静态包:估算]` |
65
+ | **证据链** | 渲染层 | 每个数字都带**来源标签**:`[骨架:openflights]`=公开航线数据校验过"这条线能飞";`[实时API:flyai]`=刚从实时接口拉的现价现况;`[静态包:估算]`=非实时的调研估算(**下单前请核实**)。降级时标签如实更换,估算绝不冒充实时 |
66
66
 
67
67
  **不像普通 AI 聊天**——LLM 只做理解和解释,**判定与算术是数学求解器**算的。
68
68
 
@@ -92,11 +92,13 @@ GoTry: 收到。先把约束记下来——
92
92
  [静态包:估算] G7315/G7316 价格按 7-8 月淡季估算
93
93
  ```
94
94
 
95
- > **Brief English summary**: input → engine verdicts (feasibility + whole-cost) → recommendation + wish-pool entry for infeasible candidates. Every numeric carries an evidence tag.
95
+ > 标签导读:`[骨架:openflights]` 说的是"这条航线能飞"已被公开航线数据校验;`[实时API:*]` 说的是刚从实时接口拉回的当下数据;`[静态包:估算]` 提醒价格是淡季档估算——**订前核实**。
96
+
97
+ > **Brief English summary**: input → engine verdicts (feasibility + whole-cost) → recommendation + wish-pool entry for infeasible candidates. Every numeric carries an evidence tag. See the 证据链 row above for what each tag means.
96
98
 
97
99
  ---
98
100
 
99
- ## 🧰 18 个工具 — Tools
101
+ ## 🧰 20 个工具 — Tools
100
102
 
101
103
  | 组 | 工具 | 干什么 |
102
104
  |---|---|---|
@@ -112,6 +114,7 @@ GoTry: 收到。先把约束记下来——
112
114
  | **记忆与触达** | `gotry_motivation_save` | 动机画像落盘(evidence 强制,反幻觉) |
113
115
  | | `gotry_wish_pool_add` / `gotry_wish_pool_list` | 「下一次出发」愿望池 + 0..1 条件召回 |
114
116
  | | `gotry_companion_save` · `gotry_trip_log` | 同行人档案 / 旅行时间线 |
117
+ | **产物** | `gotry_artifacts_list` / `gotry_artifacts_read` | 发现与查看已生成的产物(异步交付 + 工作目录 markdown),行号文件视图,只读 |
115
118
  | **通用外部** | `gotry_web_search` · `gotry_video_subtitle` · `gotry_github_search` · `gotry_agent_reach` | 网页/字幕/GitHub/全渠道外部信息(经 Agent-Reach) |
116
119
 
117
120
  ---
@@ -134,7 +137,7 @@ GoTry: 收到。先把约束记下来——
134
137
  ```
135
138
  ┌──────────────────────────────────────────────────────────────┐
136
139
  │ L1 对话即界面 chat-as-UI; gates 是消息内选择题 │
137
- │ L2 编排 dsh 运行时 + GoTry 插件(ReAct);18 个工具 │
140
+ │ L2 编排 dsh 运行时 + GoTry 插件(ReAct);20 个工具 │
138
141
  │ L3 领域 统一行程模型 + Z3 可行性引擎(枚举/Z3 双形态) │
139
142
  │ L4 数据 静态数据包 + hotelbyte-cli 实时桥 + OpenFlights 骨架 │
140
143
  │ L5 治理 LoopX(objective / gates / evidence / quota) │
@@ -143,9 +146,9 @@ GoTry: 收到。先把约束记下来——
143
146
 
144
147
  | 层 | 模块 | 角色 |
145
148
  |---|---|---|
146
- | L2 | `ts/src/index.ts`(dsh 插件) | 注册 18 工具,挂时间锚点/记忆 brief 变量;execute 异常隔离 + 授权闸 + 进程护栏 |
149
+ | L2 | `ts/src/index.ts`(dsh 插件) | 注册 20 工具,挂时间锚点/记忆 brief 变量;execute 异常隔离 + 授权闸 + 进程护栏 |
147
150
  | L3 | `ts/src/unified.ts` · `py/gotry_feasibility/` | 唯一求解入口(候选枚举 + 航班链 Z3) |
148
- | L4 | `ts/capabilities/hbcli.ts` · `skeleton-check.ts` | 实时库存桥 + OpenFlights 骨架(三值语义) |
151
+ | L4 | `ts/capabilities/effect.ts` · `hbcli.ts` · `skeleton-check.ts` | 效应解译层(退避重试/断路器/mock 解译器,issue #16)+ 实时库存桥 + OpenFlights 骨架(三值语义) |
149
152
  | L5 | loopx 治理面 | objective / gates / evidence / quota |
150
153
 
151
154
  > 📖 完整 ADR / 演进 / 债务清单: [`docs/architecture.md`](docs/architecture.md)
@@ -154,24 +157,23 @@ GoTry: 收到。先把约束记下来——
154
157
 
155
158
  ## ⚠️ 状态与限制 — Status & limitations
156
159
 
157
- **已就绪(全绿可验)**:
160
+ **今天可用的能力**(全栈回归 §1–§34 全绿,每项都有确定性测试):
158
161
 
159
- - **Z3 WASM race 已根治**(`z3-shared.ts` 单实例+会话级互斥;run-all §30 并发回归闸)
160
- - ✅ **实时机票/酒店/会话检索**已接入:FlyAI 官方通道(机/火/酒;`GOTRY_REALTIME_PRICING=1` 可选把实时票价覆写进求解)
161
- - **英文面(工程层)**:`GOTRY_LOCALE=en` 一键切换,en 零缺键,金标准行为不变
162
- - **账号会话:授权闸 + 登录产品化 + 自动检测**(见上方 [🔐 账号会话](#-账号会话授权与隐私--account-consent))
163
- - ✅ License MIT;薄壳遗留已删除(dsh web 唯一产品面)
162
+ - **Z3 求解引擎** —— 可行性判定 + 门到门全成本;历史并发竞态已根治(§30 并发回归闸)
163
+ - **实时检索**:机票/火车/酒店(飞猪官方通道)、目的地/酒店目录、天气、航班观测、通航性校验;可选让实时票价覆写进求解(`GOTRY_REALTIME_PRICING=1`)
164
+ - **账号会话检索**:你本人登录态查携程机票,授权与隐私规则见上方 **🔐 账号会话:授权与隐私** 小节
165
+ - **记忆与触达**:动机画像 / 愿望池 / 同行人 / 旅行时间线;英文输出一键切换(`GOTRY_LOCALE=en`)
164
166
 
165
- **未收口(诚实清单)**:
167
+ **已知限制**(截至 2026-08-29,诚实清单):
166
168
 
167
- - ⏳ **M3 Exit 未关闭** —— 工程与分发面就绪,但真实种子用户 evidence(50–200 人 cohort)未积累;合成 fixture 只证明合同不证明 business pass
168
- - ⏳ **中文之外的界面残余** —— dsh web 界面属宿主;工具结果卡与人格对话面的英文校准件挂 M4 校准样本
169
- - ⏳ **携程酒店/美团会话适配器** —— 机票已通,酒店会话面等登录态实测回填(下一 tick)
169
+ - ⏳ **M3 Exit 未关闭** —— 工程与分发面就绪,但"真实种子用户"证据(50–200 人 cohort)尚未积累;自动化测试证明的是合同与公式,不是 business pass
170
+ - ⏳ **携程酒店 / 美团的登录态会话适配** —— 机票已通,酒店面等登录态实测回填(下一个 tick)
171
+ - ⏳ **界面语言** —— 英文界面仅覆盖求解确定性输出层;dsh 宿主界面与对话面属宿主/校准件
170
172
 
171
173
  <details>
172
- <summary>📖 展开完整状态表(账本/证据合同/里程碑口径)</summary>
174
+ <summary>📖 更深的工程状态(账本合同 / 证据合同 / 里程碑口径)</summary>
173
175
 
174
- 事务化状态账本(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)。
176
+ 状态权威面在这里,README 不展开:事务化状态账本(ADR-15)+ 双形态冻结(ADR-16:本地+Web 一套账本语义);M3 真实 cohort 证据合同已立(fixture 不充当 Exit,真实 50–200 人样本即开 Exit);M4 paired-cohort 价值证据合同(run-all §34)合成数据不充当 Exit 证据;异步工单终态合同(`gotry_async_terminal.v1`:4/4→succeeded/ledger settled/exit 0)。细则见 [`docs/roadmap.md`](docs/roadmap.md) / [`docs/architecture.md`](docs/architecture.md) §1 与 #19–#22
175
177
 
176
178
  </details>
177
179
 
@@ -230,4 +232,4 @@ GoTry: 收到。先把约束记下来——
230
232
 
231
233
  **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
232
234
 
233
- **Last verified against `v0.0.1-rc.14`(2026-08-29)** — 全栈回归全绿 §1-§34(发布流程见 `scripts/publish-npm.sh`)。
235
+ **Last verified against `v0.0.1-rc.15`(2026-08-29)** — 全栈回归全绿 §1-§36(发布流程见 `scripts/publish-npm.sh`)。
@@ -4,6 +4,10 @@
4
4
  * hbcli(hotelbyte-cli) → 官方 install.sh(原生二进制,~/.local/bin/hbcli)
5
5
  * agent-reach → 官方 pip 安装 git+ upstream(包内 .venv,与 z3-solver 同址原则)
6
6
  * flyai → 无需安装(npx 每次自拉 @fly-ai/flyai-cli)
7
+ * dsh-better-sidebar → dsh 宿主层插件市场组件(dshmarket.com #1 UI,18.9 万周装):
8
+ * dsh web 侧栏工作台(文件浏览/Markdown/Mermaid/PDF 预览)——
9
+ * gotry 产物(工单交付 md/行程 md)的成熟查看面(issue #25)。
10
+ * 宿主层安装(dsh plugin → ~/.dsh/profiles/web),不进 gotry 依赖。
7
11
  *
8
12
  * 用法:
9
13
  * node bin/gotry-bootstrap.js # 显式安装(缺啥装啥;失败 exit 1)
@@ -14,6 +18,7 @@
14
18
  * GOTRY_SETUP_SKIP=1 全部跳过
15
19
  * GOTRY_SETUP_HBCLI=0 跳过 hbcli
16
20
  * GOTRY_SETUP_REACH=0 跳过 agent-reach
21
+ * GOTRY_SETUP_SIDEBAR=0 跳过 dsh-better-sidebar
17
22
  *
18
23
  * 契约:安装外部依赖永远不挡 gotry 本体——能力层各有降级路径(静态包/not-installed
19
24
  * verdict),自举失败只降级体验,不产生故障。凭证(hbcli auth / agent-reach 渠道
@@ -93,10 +98,42 @@ async function setupReach() {
93
98
  return { ok: true }
94
99
  }
95
100
 
101
+ async function setupSidebar() {
102
+ say('[gotry-setup] dsh-better-sidebar(dsh web 侧栏工作台,产物查看面 issue #25)')
103
+ const installed = existsSync(join(homedir(), '.dsh/profiles/web/node_modules/dsh-better-sidebar/package.json'))
104
+ if (installed) { say(' ✓ 已安装(~/.dsh/profiles/web)'); return { ok: true } }
105
+ if (CHECK_ONLY) { say(' ✗ 未安装(--check-only 只报告)'); return { ok: true } }
106
+ const SIDEBAR_PKG = 'dsh-better-sidebar@latest'
107
+ // dsh CLI:本包依赖里的 @deepseek-ai/dsh 优先;解析不到走 npx 自拉(官方安装途径同款)
108
+ const { createRequire } = await import('node:module')
109
+ const require_ = createRequire(join(repoRoot, 'package.json'))
110
+ let launched = false
111
+ try {
112
+ const dshBin = require_.resolve('@deepseek-ai/dsh/lib/bin.js')
113
+ say(` 安装中(dsh plugin → web profile): ${SIDEBAR_PKG}`)
114
+ const r = await run(process.execPath, [dshBin, 'plugin', '--profile', 'web', 'add', SIDEBAR_PKG], { timeoutMs: 300_000 })
115
+ launched = true
116
+ if (r.ok) {
117
+ say(' ✓ 安装完成(gotry web 刷新浏览器即见右侧工作台;工作区里的产物 md 可直接预览)')
118
+ return { ok: true }
119
+ }
120
+ say(` ✗ dsh plugin 安装失败(${r.error})——尝试 npx 途径`)
121
+ } catch { /* 本包未携带 @deepseek-ai/dsh */ }
122
+ if (!launched) say(' 本包未携带 @deepseek-ai/dsh,走 npx 途径安装')
123
+ const r2 = await run('npx', ['-y', '--package', '@deepseek-ai/dsh', 'dsh', 'plugin', '--profile', 'web', 'add', SIDEBAR_PKG], { timeoutMs: 300_000 })
124
+ if (!r2.ok) {
125
+ say(' ✗ 安装失败——不影响 gotry:产物仍可在对话里说「看看我生成的行程」经 gotry_artifacts_list/read 查看;可稍后重试: npx gotry setup')
126
+ return { ok: false }
127
+ }
128
+ say(' ✓ 安装完成(gotry web 刷新浏览器即见右侧工作台)')
129
+ return { ok: true }
130
+ }
131
+
96
132
  async function main() {
97
133
  if (process.platform === 'win32') {
98
134
  say('[gotry-setup] Windows 暂不支持自动安装(hbcli 上游仅 darwin/linux)。手动指引:')
99
135
  say(` hbcli: ${HBCLI_INSTALL_CMD}(WSL);agent-reach: python -m venv .venv && .venv/Scripts/pip install ${REACH_INSTALL_URL}`)
136
+ say(' dsh-better-sidebar: npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@latest')
100
137
  process.exit(AUTO ? 0 : 1)
101
138
  }
102
139
  if (AUTO && (process.env.CI || process.env.GOTRY_SETUP_SKIP === '1')) {
@@ -110,6 +147,8 @@ async function main() {
110
147
  else say('[gotry-setup] hbcli:GOTRY_SETUP_HBCLI=0 跳过')
111
148
  if (process.env.GOTRY_SETUP_REACH !== '0') results.push(await setupReach())
112
149
  else say('[gotry-setup] agent-reach:GOTRY_SETUP_REACH=0 跳过')
150
+ if (process.env.GOTRY_SETUP_SIDEBAR !== '0') results.push(await setupSidebar())
151
+ else say('[gotry-setup] dsh-better-sidebar:GOTRY_SETUP_SIDEBAR=0 跳过')
113
152
  say('[gotry-setup] flyai:无需安装(npx 每次自拉 @fly-ai/flyai-cli,免 key)')
114
153
  const failed = results.filter((r) => !r.ok).length
115
154
  if (failed > 0) {
@@ -0,0 +1,217 @@
1
+ import { readdir, readFile, stat } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { isAbsolute, join, resolve, sep } from 'node:path';
4
+ import { openLedgerIfExists } from '../src/state-ledger.js';
5
+ const MAX_LIST = 50;
6
+ const MAX_WINDOW = 400;
7
+ const MAX_BYTES = 2 * 1024 * 1024;
8
+ const TEXT_EXT_LANG = {
9
+ md: 'markdown',
10
+ txt: 'text',
11
+ json: 'json',
12
+ jsonl: 'json',
13
+ csv: 'csv',
14
+ log: 'text',
15
+ yaml: 'yaml',
16
+ yml: 'yaml'
17
+ };
18
+ const DIR_DENY = [
19
+ 'node_modules',
20
+ '.git'
21
+ ];
22
+ function rootOf(stateRoot) {
23
+ return stateRoot === '.' ? process.cwd() : resolve(stateRoot);
24
+ }
25
+ function underRoot(p, root) {
26
+ const r = resolve(root);
27
+ return p === r || p.startsWith(r + sep);
28
+ }
29
+ function hasDeniedSegment(p) {
30
+ return p.split(sep).some((seg)=>DIR_DENY.includes(seg));
31
+ }
32
+ function asyncDeliverablePath(root, id) {
33
+ return join(root, 'gotry-state', 'async', `${id}.deliverable.md`);
34
+ }
35
+ function listRunsFromLedger(root, tenant, limit) {
36
+ const ledger = openLedgerIfExists(root, tenant);
37
+ if (!ledger) return [];
38
+ const rows = ledger.db.prepare('SELECT id, goal, status, deliverable, updated FROM workflow_runs WHERE tenant_id = ? ORDER BY updated DESC LIMIT ?').all(ledger.tenant, limit);
39
+ return rows.map((r)=>{
40
+ const file = asyncDeliverablePath(root, r.id);
41
+ return {
42
+ source: 'async-run',
43
+ id: r.id,
44
+ title: r.goal,
45
+ path: file,
46
+ status: r.status,
47
+ updated: r.updated,
48
+ bytes: r.deliverable?.length
49
+ };
50
+ });
51
+ }
52
+ async function listDeliverableFiles(root, limit) {
53
+ const dir = join(root, 'gotry-state', 'async');
54
+ let names = [];
55
+ try {
56
+ names = (await readdir(dir)).filter((n)=>n.endsWith('.deliverable.md'));
57
+ } catch {
58
+ return [];
59
+ }
60
+ const entries = [];
61
+ for (const n of names.slice(0, limit * 2)){
62
+ const p = join(dir, n);
63
+ const st = await stat(p).catch(()=>null);
64
+ if (!st?.isFile()) continue;
65
+ entries.push({
66
+ source: 'async-run',
67
+ id: n.replace(/\.deliverable\.md$/, ''),
68
+ title: n.replace(/\.deliverable\.md$/, ''),
69
+ path: p,
70
+ status: existsSync(p.replace(/\.deliverable\.md$/, '.json')) ? 'pending-view' : 'legacy',
71
+ updated: new Date(st.mtimeMs).toISOString(),
72
+ bytes: st.size
73
+ });
74
+ }
75
+ return entries.sort((a, b)=>String(b.updated).localeCompare(String(a.updated)));
76
+ }
77
+ async function listCwdMarkdown(cwd, limit) {
78
+ let dirents;
79
+ try {
80
+ dirents = await readdir(cwd, {
81
+ withFileTypes: true
82
+ });
83
+ } catch {
84
+ return [];
85
+ }
86
+ const entries = [];
87
+ for (const d of dirents){
88
+ if (!d.isFile() || !/\.md$/i.test(d.name) || d.name.startsWith('.')) continue;
89
+ const p = join(cwd, d.name);
90
+ const st = await stat(p).catch(()=>null);
91
+ if (!st) continue;
92
+ entries.push({
93
+ source: 'cwd-file',
94
+ id: d.name,
95
+ title: d.name.replace(/\.md$/i, ''),
96
+ path: p,
97
+ updated: new Date(st.mtimeMs).toISOString(),
98
+ bytes: st.size
99
+ });
100
+ }
101
+ return entries.sort((a, b)=>String(b.updated).localeCompare(String(a.updated))).slice(0, limit);
102
+ }
103
+ export async function listArtifacts(opts) {
104
+ const limit = Math.max(1, Math.min(opts.limit ?? 20, MAX_LIST));
105
+ const root = rootOf(opts.stateRoot);
106
+ const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
107
+ const seenPath = new Set();
108
+ const merged = [];
109
+ for (const e of [
110
+ ...listRunsFromLedger(root, 'local', limit),
111
+ ...await listDeliverableFiles(root, limit),
112
+ ...await listCwdMarkdown(cwd, limit)
113
+ ]){
114
+ if (seenPath.has(e.path)) continue;
115
+ seenPath.add(e.path);
116
+ merged.push(e);
117
+ }
118
+ merged.sort((a, b)=>String(b.updated ?? '').localeCompare(String(a.updated ?? '')));
119
+ const total = merged.length;
120
+ return {
121
+ artifacts: merged.slice(0, limit),
122
+ total,
123
+ truncated: total > limit,
124
+ roots: [
125
+ root,
126
+ cwd
127
+ ]
128
+ };
129
+ }
130
+ export async function readArtifact(opts) {
131
+ const root = rootOf(opts.stateRoot);
132
+ const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
133
+ const raw = String(opts.path ?? '').trim();
134
+ if (!raw) return {
135
+ ok: false,
136
+ error: 'path 必填(来自 gotry_artifacts_list 的 path,或异步工单 id)'
137
+ };
138
+ let text = null;
139
+ let filePath = '';
140
+ if (!raw.includes('/') && !raw.includes('\\') && !raw.includes('.')) {
141
+ const ledger = openLedgerIfExists(root, 'local');
142
+ const run = ledger?.db.prepare('SELECT id, goal, status, deliverable FROM workflow_runs WHERE id = ? AND tenant_id = ?').get(raw, ledger.tenant);
143
+ if (run?.deliverable) {
144
+ text = run.deliverable;
145
+ filePath = asyncDeliverablePath(root, raw);
146
+ } else {
147
+ const p = asyncDeliverablePath(root, raw);
148
+ if (existsSync(p)) {
149
+ filePath = p;
150
+ text = await readFile(p, 'utf-8');
151
+ }
152
+ }
153
+ if (text === null) return {
154
+ ok: false,
155
+ error: `工单 ${raw} 无 deliverable(未交付或不存在)`,
156
+ hint: '先 gotry_artifacts_list 看在册产物'
157
+ };
158
+ }
159
+ if (text === null) {
160
+ const candidates = isAbsolute(raw) ? [
161
+ resolve(raw)
162
+ ] : [
163
+ resolve(cwd, raw),
164
+ resolve(root, raw),
165
+ resolve(root, 'gotry-state', 'async', raw)
166
+ ];
167
+ const allowed = candidates.find((p)=>(underRoot(p, cwd) || underRoot(p, root)) && !hasDeniedSegment(p));
168
+ if (!allowed) {
169
+ return {
170
+ ok: false,
171
+ error: `路径越界:${raw}`,
172
+ hint: `只读 ${root} 与 dsh 工作目录内的文本产物`
173
+ };
174
+ }
175
+ const ext = allowed.slice(allowed.lastIndexOf('.') + 1).toLowerCase();
176
+ if (!TEXT_EXT_LANG[ext]) {
177
+ return {
178
+ ok: false,
179
+ error: `不支持的文件类型 .${ext}`,
180
+ hint: `白名单:${Object.keys(TEXT_EXT_LANG).join('/')}`
181
+ };
182
+ }
183
+ const st = await stat(allowed).catch(()=>null);
184
+ if (!st?.isFile()) return {
185
+ ok: false,
186
+ error: `文件不存在:${raw}`,
187
+ hint: '先 gotry_artifacts_list 看在册产物'
188
+ };
189
+ if (st.size > MAX_BYTES) return {
190
+ ok: false,
191
+ error: `文件过大(${st.size} bytes > ${MAX_BYTES})`
192
+ };
193
+ filePath = allowed;
194
+ text = await readFile(allowed, 'utf-8');
195
+ }
196
+ const allLines = text.split('\n');
197
+ const offset = Math.max(1, Math.min(opts.offset ?? 1, allLines.length));
198
+ const limit = Math.max(1, Math.min(opts.limit ?? MAX_WINDOW, MAX_WINDOW));
199
+ const slice = allLines.slice(offset - 1, offset - 1 + limit);
200
+ const ext = filePath.slice(filePath.lastIndexOf('.') + 1).toLowerCase();
201
+ return {
202
+ ok: true,
203
+ path: filePath,
204
+ offset,
205
+ lines: slice.map((t, i)=>({
206
+ number: offset + i,
207
+ text: t
208
+ })),
209
+ totalLines: allLines.length,
210
+ lang: TEXT_EXT_LANG[ext] ?? 'text',
211
+ content: slice.join('\n'),
212
+ windowed: allLines.length > offset - 1 + slice.length
213
+ };
214
+ }
215
+
216
+
217
+ //# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/artifacts.ts