dsh-tabbit 0.2.2 → 0.3.0
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/CHANGELOG.md +100 -0
- package/LICENSE +21 -0
- package/README.en.md +116 -0
- package/README.md +55 -81
- package/client/client.js +389 -0
- package/cordis.patch.yml +77 -2
- package/lib/core/index.js +742 -0
- package/lib/installer/detect.js +374 -0
- package/lib/installer/download.js +247 -0
- package/lib/installer/index.js +254 -0
- package/lib/mentions/index.js +595 -0
- package/lib/permissions/index.js +136 -0
- package/lib/runtime/cli.js +229 -0
- package/lib/runtime/client.js +431 -0
- package/lib/runtime/codec.js +126 -0
- package/lib/runtime/endpoint.js +248 -0
- package/lib/runtime/errors.js +126 -0
- package/lib/runtime/instances.js +287 -0
- package/lib/runtime/net.js +143 -0
- package/lib/runtime/peer.js +132 -0
- package/lib/tool-browser/index.js +425 -0
- package/lib/update-check.js +343 -0
- package/lib/web-fetch/index.js +219 -0
- package/package.json +53 -16
- package/skills/tabbit/SKILL.md +66 -0
- package/skills/tabbit/references/interaction-helpers.md +150 -0
- package/skills/tabbit/references/platform-invocation.md +174 -0
- package/skills/{tabbit-browser → tabbit}/references/playwright-recipes.md +11 -3
- package/skills/tabbit/references/runtime-recovery.md +104 -0
- package/README.zh-CN.md +0 -114
- package/index.js +0 -352
- package/installer.js +0 -568
- package/skills/tabbit-browser/SKILL.md +0 -274
- package/skills/tabbit-browser/agents/openai.yaml +0 -4
- package/skills/tabbit-browser/references/interaction-helpers.md +0 -103
- package/skills/tabbit-browser/references/platform-invocation.md +0 -45
- package/skills/tabbit-browser/references/runtime-recovery.md +0 -95
- package/update-check.js +0 -177
- /package/skills/{tabbit-browser → tabbit}/references/information-extraction.md +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* 文件职责:`tabbit_browser_install` 与 `tabbit_plugin_update` 两个运维工具
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 【tabbit_browser_install】让模型在第一次动 `tabbit_browser` 之前先跑一次
|
|
7
|
+
* 体检:机器上装没装稳定版 Tabbit?版本够不够(最低 1.9.0)?Runtime
|
|
8
|
+
* Service 通不通?三种结论:
|
|
9
|
+
* - ready 一切就绪(结果按会话缓存,之后调用秒回);
|
|
10
|
+
* - restart-required 装了、版本也够,但 Runtime Service 不可达——请用户
|
|
11
|
+
* 重启一次 Tabbit(launcher/endpoint 是浏览器启动时注册的);
|
|
12
|
+
* - background 没装或版本太旧——已在后台开始下载对应地区的安装器
|
|
13
|
+
* (国际版 tabbit.ai / 国内版 tabbit.com),下好后引导
|
|
14
|
+
* 用户自己运行安装。
|
|
15
|
+
*
|
|
16
|
+
* 【tabbit_plugin_update】插件自身更新检查的显式入口(逻辑在
|
|
17
|
+
* ../update-check.ts):记录用户拒绝过的版本 / 跳过日缓存强制重查。日常
|
|
18
|
+
* 通知不靠这个工具——skill 每次加载时自动带出更新通知(core 的 provider)。
|
|
19
|
+
*
|
|
20
|
+
* 出处:自 github:Tabbit-Browser/dsh-tabbit(本包 npm 0.2.x 世代)移植。
|
|
21
|
+
* 与 0.2.x 的两点刻意差异:
|
|
22
|
+
* 1. 不再报告 CLI 沙箱/Full-Permission 状态——那个世代的自动化走 Bash 调
|
|
23
|
+
* CLI,要关心 shell 沙箱;我们的 `tabbit_browser` 是 dsh 原生工具,在
|
|
24
|
+
* dsh 宿主进程里直接 spawn CLI,链路里没有 shell 沙箱;
|
|
25
|
+
* 2. "Runtime Service 在不在跑"以 ctx.tabbit 的实例注册表为主信号
|
|
26
|
+
* (endpoint 文件只在服务运行期间存在,严格更强),注册表读不到实例时
|
|
27
|
+
* 才降级为 0.2.x 的进程探测(Windows 上注册表位置未经真机确认,降级
|
|
28
|
+
* 路径是那里的主要在线判据;见 detect.ts 文件头)。
|
|
29
|
+
*
|
|
30
|
+
* ─── 必备背景:dsh 的 jobs(后台任务)服务 ──────────────────────────────
|
|
31
|
+
* `ctx.jobs.start({kind, label, owner, run})` 启动一个后台任务:
|
|
32
|
+
* - run() 返回一个句柄 {cancel, done, readOutput}(见 download.ts 的
|
|
33
|
+
* createDownloadJob);
|
|
34
|
+
* - dsh 会轮询 readOutput() 把输出渐进展示给用户、在 done 落定时通知——
|
|
35
|
+
* 也就是说下载进度和完成提醒【不用我们自己做 UI】,挂上 jobs 就有;
|
|
36
|
+
* - owner 关联到发起的 Agent;ctx.jobs.get(jobId, owner) 可查快照状态。
|
|
37
|
+
*/
|
|
38
|
+
import { existsSync } from 'node:fs';
|
|
39
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
40
|
+
import { detectTabbitInstallations, detectTabbitRuntimeProcesses, MINIMUM_TABBIT_VERSION, summarizeTabbitRuntime, } from './detect.js';
|
|
41
|
+
import { createDownloadJob } from './download.js';
|
|
42
|
+
import { checkPluginUpdate, dismissUpdate, isBrowserManagedInstall, messageForUpdate } from '../update-check.js';
|
|
43
|
+
export const name = 'tabbit-installer';
|
|
44
|
+
export const inject = ['tools', 'jobs', 'tabbit'];
|
|
45
|
+
export function apply(ctx) {
|
|
46
|
+
registerInstallerTool(ctx);
|
|
47
|
+
registerUpdateTool(ctx);
|
|
48
|
+
}
|
|
49
|
+
export function registerInstallerTool(ctx, overrides = {}) {
|
|
50
|
+
const detect = overrides.detect ?? detectTabbitInstallations;
|
|
51
|
+
const detectRuntime = overrides.detectRuntime ?? detectTabbitRuntimeProcesses;
|
|
52
|
+
// 两张按 Agent 记账的表。用 WeakMap 的原因:键是 Agent 对象本身,会话
|
|
53
|
+
// 销毁、Agent 被垃圾回收时对应条目自动消失,不需要手动清理。
|
|
54
|
+
/* Agent → 已缓存的 ready 结果("每会话只真查一次")。 */
|
|
55
|
+
const readyByOwner = new WeakMap();
|
|
56
|
+
/* Agent → 进行中的下载 jobId(防止重复起下载)。 */
|
|
57
|
+
const activeJobs = new WeakMap();
|
|
58
|
+
ctx.tools.register(defineTool({
|
|
59
|
+
name: 'tabbit_browser_install',
|
|
60
|
+
// 给模型的使用协议:每会话在首次浏览器操作前调一次;ready 结果整会话
|
|
61
|
+
// 有效;只有 launcher/Runtime 报错或用户装了新版本后才带 refresh 重查。
|
|
62
|
+
description: [
|
|
63
|
+
'Check for a supported stable Tabbit Browser install',
|
|
64
|
+
`(version ${MINIMUM_TABBIT_VERSION} or newer) and a reachable Runtime Service before`,
|
|
65
|
+
'using `tabbit_browser`. Call this once before the first browser operation in a',
|
|
66
|
+
"session; a `ready` result is cached for that whole session. Pass refresh only",
|
|
67
|
+
'after a `tabbit_browser` launcher/Runtime failure or a Tabbit install/update.',
|
|
68
|
+
'Downloads the region-appropriate installer in the background when Tabbit is',
|
|
69
|
+
'missing or outdated; otherwise reports when the browser must be restarted once.',
|
|
70
|
+
].join(' '),
|
|
71
|
+
parameters: {
|
|
72
|
+
refresh: {
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
description: "Discard this agent session's cached result and recheck everything. Use only after a launcher/Runtime failure or an install change.",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
output: {
|
|
78
|
+
schema: { type: 'json' },
|
|
79
|
+
// 渲染:只把 message 文案给模型(结构化字段仍在结果 JSON 里)。
|
|
80
|
+
render: (_args, value) => [{ type: 'text', text: value.message }],
|
|
81
|
+
},
|
|
82
|
+
// 纯检测、无副作用冲突,声明并发安全(dsh 可与其它工具并行跑它)。
|
|
83
|
+
isConcurrencySafe: () => true,
|
|
84
|
+
async execute(args, exec) {
|
|
85
|
+
const owner = exec.agent;
|
|
86
|
+
// ── 第 1 步:会话缓存 ────────────────────────────────────────────
|
|
87
|
+
if (owner !== undefined && args.refresh === true)
|
|
88
|
+
readyByOwner.delete(owner);
|
|
89
|
+
if (owner !== undefined && args.refresh !== true) {
|
|
90
|
+
const cached = readyByOwner.get(owner);
|
|
91
|
+
if (cached) {
|
|
92
|
+
return {
|
|
93
|
+
...cached,
|
|
94
|
+
cached: true,
|
|
95
|
+
message: "Reused this session's cached environment check. The tabbit_browser tool is ready to use.",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ── 第 2 步:查有没有还在跑的下载(避免模型重复调用起一堆下载)──
|
|
100
|
+
const existingJobId = owner !== undefined ? activeJobs.get(owner) : undefined;
|
|
101
|
+
if (existingJobId !== undefined) {
|
|
102
|
+
try {
|
|
103
|
+
const snapshot = ctx.jobs.get(existingJobId, owner);
|
|
104
|
+
if (snapshot.status === 'running' || snapshot.status === 'stopping') {
|
|
105
|
+
return {
|
|
106
|
+
status: 'background',
|
|
107
|
+
message: `A Tabbit installer download is already running as ${existingJobId}. DSH will report progress and notify you when it's ready.`,
|
|
108
|
+
cached: false,
|
|
109
|
+
jobId: String(existingJobId),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// job 已经不存在(被清理了):把陈账删掉继续走正常流程。
|
|
115
|
+
if (owner !== undefined)
|
|
116
|
+
activeJobs.delete(owner);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ── 第 3 步:安装检测(detect.ts:mac plist / win 注册表读版本)──
|
|
120
|
+
const detected = await detect();
|
|
121
|
+
if (detected.supportedInstallations.length === 0) {
|
|
122
|
+
// 没装 or 版本不够 → 起后台下载。
|
|
123
|
+
const downloadReason = detected.installations.length === 0
|
|
124
|
+
? 'No stable Tabbit edition is installed.'
|
|
125
|
+
: `Installed stable Tabbit version(s) do not meet the minimum ${MINIMUM_TABBIT_VERSION}: ${detected.installations.map((item) => `${item.name} ${item.version ?? 'unknown'}`).join(', ')}.`;
|
|
126
|
+
let jobId;
|
|
127
|
+
try {
|
|
128
|
+
jobId = ctx.jobs.start({
|
|
129
|
+
kind: 'tabbit-installer',
|
|
130
|
+
label: 'Download the region-appropriate Tabbit Browser installer',
|
|
131
|
+
outputLimitBytes: 16 * 1024,
|
|
132
|
+
owner,
|
|
133
|
+
// run 返回 download.ts 包好的 jobs 句柄;onSettled 里把 activeJobs
|
|
134
|
+
// 的账销掉(只销"还是这单"的账,防止竞态覆盖后来者)。
|
|
135
|
+
run: () => createDownloadJob({ onSettled: () => { if (owner !== undefined && activeJobs.get(owner) === jobId)
|
|
136
|
+
activeJobs.delete(owner); } }),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
// 连 jobs 都起不来:退化为指导用户自己去官网下载。
|
|
141
|
+
return {
|
|
142
|
+
status: 'background',
|
|
143
|
+
message: `Environment check failed: ${downloadReason} Could not start a background download (${String(error?.message ?? error)}). Ask the user to download and run the Tabbit Browser installer from tabbit.ai (or tabbit.com in mainland China) themselves.`,
|
|
144
|
+
cached: false,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (owner !== undefined)
|
|
148
|
+
activeJobs.set(owner, jobId);
|
|
149
|
+
return {
|
|
150
|
+
status: 'background',
|
|
151
|
+
message: `Environment check failed: ${downloadReason} Started the region-appropriate Tabbit Browser installer download as ${jobId}. DSH will report progress and notify you when the installer is ready — tell the user the installer path and ask them to run it, then relaunch Tabbit Browser once.`,
|
|
152
|
+
cached: false,
|
|
153
|
+
jobId: String(jobId),
|
|
154
|
+
minimumVersion: MINIMUM_TABBIT_VERSION,
|
|
155
|
+
installations: detected.installations,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// ── 第 4 步:Runtime Service 可达性 ────────────────────────────────
|
|
159
|
+
// 主信号:ctx.tabbit 的实例注册表(endpoint 在线 = 服务在跑)。
|
|
160
|
+
// 降级信号:注册表里一个实例都没有时(典型是 Windows——注册表目录
|
|
161
|
+
// 位置未经真机确认),扫进程表找 Runtime 常驻进程(见 detect.ts)。
|
|
162
|
+
const launcherPath = ctx.tabbit.launcherPath();
|
|
163
|
+
const launcherReady = existsSync(launcherPath);
|
|
164
|
+
const instances = ctx.tabbit.instances();
|
|
165
|
+
const onlineInstances = instances.filter((instance) => instance.online);
|
|
166
|
+
const runtime = summarizeTabbitRuntime(instances.length === 0 ? detectRuntime() : []);
|
|
167
|
+
const versions = detected.supportedInstallations.map((item) => `${item.name} ${item.version ?? ''}`.trim()).join(', ');
|
|
168
|
+
if (launcherReady && (onlineInstances.length > 0 || runtime.running)) {
|
|
169
|
+
// 全绿:ready,写入会话缓存。多实例(注册表在线数或进程数 >1)时
|
|
170
|
+
// 顺带提醒可能需要选实例。
|
|
171
|
+
const ambiguityNote = onlineInstances.length > 1
|
|
172
|
+
? ` Multiple Tabbit instances are online (${onlineInstances.length}); run /tabbit-info and set settings key tabbit.instance if the wrong one gets picked.`
|
|
173
|
+
: runtime.ambiguous
|
|
174
|
+
? ` Multiple Tabbit runtime processes are running (${runtime.instanceCount}); set settings key tabbit.instance if the wrong one gets picked.`
|
|
175
|
+
: '';
|
|
176
|
+
const result = {
|
|
177
|
+
status: 'ready',
|
|
178
|
+
message: `Browser installation and Runtime Service checks passed (${versions}).${ambiguityNote} The tabbit_browser tool is ready to use.`,
|
|
179
|
+
cached: false,
|
|
180
|
+
minimumVersion: MINIMUM_TABBIT_VERSION,
|
|
181
|
+
onlineInstanceCount: onlineInstances.length,
|
|
182
|
+
...(instances.length === 0 ? { runtimeProcessCount: runtime.instanceCount } : {}),
|
|
183
|
+
installations: detected.supportedInstallations,
|
|
184
|
+
};
|
|
185
|
+
if (owner !== undefined)
|
|
186
|
+
readyByOwner.set(owner, result);
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
// 装了、版本够,但 launcher 缺失或两路信号都说没在跑:重启一次浏览器
|
|
190
|
+
// 就好(launcher 和 endpoint 都是浏览器启动时注册/发布的)。
|
|
191
|
+
return {
|
|
192
|
+
status: 'restart-required',
|
|
193
|
+
message: `Environment check failed: ${versions} meets the minimum version ${MINIMUM_TABBIT_VERSION}, but its Runtime Service is not reachable${launcherReady ? '' : ` (launcher not found at ${launcherPath})`}. Ask the user to relaunch Tabbit Browser once, then call this tool again with refresh: true.`,
|
|
194
|
+
cached: false,
|
|
195
|
+
minimumVersion: MINIMUM_TABBIT_VERSION,
|
|
196
|
+
onlineInstanceCount: onlineInstances.length,
|
|
197
|
+
installations: detected.supportedInstallations,
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
export function registerUpdateTool(ctx, overrides = {}) {
|
|
203
|
+
const checkUpdate = overrides.checkUpdate ?? checkPluginUpdate;
|
|
204
|
+
const dismiss = overrides.dismiss ?? dismissUpdate;
|
|
205
|
+
const env = overrides.env ?? process.env;
|
|
206
|
+
ctx.tools.register(defineTool({
|
|
207
|
+
name: 'tabbit_plugin_update',
|
|
208
|
+
description: [
|
|
209
|
+
'Record that the user declined an offered dsh-tabbit plugin version, or force a',
|
|
210
|
+
'recheck of the latest published npm release. The skill already loads an update notice',
|
|
211
|
+
'automatically when a newer version exists; call this tool only after the user declines',
|
|
212
|
+
'an offered version, or after a plugin update or connectivity change.',
|
|
213
|
+
].join(' '),
|
|
214
|
+
parameters: {
|
|
215
|
+
dismiss: {
|
|
216
|
+
type: 'string',
|
|
217
|
+
description: 'The offered version the user declined. The skill stops announcing this version; a newer release is announced again.',
|
|
218
|
+
},
|
|
219
|
+
refresh: {
|
|
220
|
+
type: 'boolean',
|
|
221
|
+
description: 'Skip the daily cache and the failure backoff and check the latest release again.',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
output: {
|
|
225
|
+
schema: { type: 'json' },
|
|
226
|
+
render: (_args, value) => [{ type: 'text', text: value.message }],
|
|
227
|
+
},
|
|
228
|
+
isConcurrencySafe: () => true,
|
|
229
|
+
async execute(args) {
|
|
230
|
+
if (isBrowserManagedInstall(env)) {
|
|
231
|
+
return {
|
|
232
|
+
status: 'browser-managed',
|
|
233
|
+
message: 'This dsh-tabbit install is managed by Tabbit Browser and updates together with the browser. Do not reinstall it via dsh plugin commands.',
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (args.dismiss !== undefined && args.dismiss !== '') {
|
|
237
|
+
await dismiss(args.dismiss);
|
|
238
|
+
return {
|
|
239
|
+
status: 'dismissed',
|
|
240
|
+
message: `Recorded that the user declined dsh-tabbit ${args.dismiss}. The skill will stop announcing this version; a newer release will be announced again.`,
|
|
241
|
+
dismissedVersion: args.dismiss,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const update = await checkUpdate({ force: args.refresh === true });
|
|
245
|
+
return {
|
|
246
|
+
status: update.status,
|
|
247
|
+
message: messageForUpdate(update),
|
|
248
|
+
...(update.currentVersion !== undefined ? { currentVersion: update.currentVersion } : {}),
|
|
249
|
+
...(update.latestVersion !== undefined ? { latestVersion: update.latestVersion } : {}),
|
|
250
|
+
...(update.changelog !== undefined ? { changelog: update.changelog } : {}),
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
}));
|
|
254
|
+
}
|