dsh-tabbit 0.2.3 → 0.3.2

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +141 -0
  4. package/README.md +70 -76
  5. package/client/client.js +390 -0
  6. package/cordis.patch.yml +76 -5
  7. package/lib/core/index.js +756 -0
  8. package/lib/installer/detect.js +374 -0
  9. package/lib/installer/download.js +247 -0
  10. package/lib/installer/index.js +254 -0
  11. package/lib/mentions/index.js +595 -0
  12. package/lib/permissions/index.js +136 -0
  13. package/lib/runtime/cli.js +229 -0
  14. package/lib/runtime/client.js +454 -0
  15. package/lib/runtime/codec.js +126 -0
  16. package/lib/runtime/endpoint.js +248 -0
  17. package/lib/runtime/errors.js +126 -0
  18. package/lib/runtime/instances.js +287 -0
  19. package/lib/runtime/net.js +143 -0
  20. package/lib/runtime/peer.js +132 -0
  21. package/lib/tool-browser/index.js +476 -0
  22. package/lib/update-check.js +343 -0
  23. package/lib/web-fetch/index.js +219 -0
  24. package/package.json +55 -16
  25. package/skills/tabbit/SKILL.md +66 -0
  26. package/skills/tabbit/references/interaction-helpers.md +150 -0
  27. package/skills/tabbit/references/platform-invocation.md +174 -0
  28. package/skills/{tabbit-browser → tabbit}/references/playwright-recipes.md +11 -3
  29. package/skills/tabbit/references/runtime-recovery.md +104 -0
  30. package/README.zh-CN.md +0 -114
  31. package/index.js +0 -352
  32. package/installer.js +0 -568
  33. package/skills/tabbit-browser/SKILL.md +0 -274
  34. package/skills/tabbit-browser/agents/openai.yaml +0 -4
  35. package/skills/tabbit-browser/references/interaction-helpers.md +0 -103
  36. package/skills/tabbit-browser/references/platform-invocation.md +0 -45
  37. package/skills/tabbit-browser/references/runtime-recovery.md +0 -95
  38. package/update-check.js +0 -177
  39. /package/skills/{tabbit-browser → tabbit}/references/information-extraction.md +0 -0
package/index.js DELETED
@@ -1,352 +0,0 @@
1
- import { readFile } from 'node:fs/promises'
2
- import { fileURLToPath } from 'node:url'
3
- import { createDownloadJob, detectTabbit } from './installer.js'
4
- import { checkPluginUpdate, dismissUpdate } from './update-check.js'
5
-
6
- const PROVIDER_NAME = 'tabbit-browser-bundled-skill'
7
- const SKILL_NAME = 'tabbit-browser'
8
- const SKILL_DESCRIPTION = 'Control the user\'s Tabbit Browser through its Browser-owned, task-isolated Playwright CLI and runtime helpers. Use for Tabbit browser automation, website interaction, extraction, QA, and benchmarks, including stable-browser version checks, runtime-process checks, and background installer download when Tabbit is absent or outdated; never silently fall back to another browser automation backend.'
9
- const SKILL_URL = new URL('./skills/tabbit-browser/SKILL.md', import.meta.url)
10
- const RESOURCE_BASE = {
11
- kind: 'directory',
12
- path: fileURLToPath(new URL('./skills/tabbit-browser/', import.meta.url)),
13
- }
14
- const INVOCATION = { modelInvocable: true, userInvocable: true }
15
-
16
- // DSH reserves rank 600 for skills shipped inside a package.
17
- const BUNDLED_SKILL_RANK = 600
18
-
19
- const candidate = {
20
- name: SKILL_NAME,
21
- description: SKILL_DESCRIPTION,
22
- invocation: INVOCATION,
23
- provider: PROVIDER_NAME,
24
- source: 'bundled',
25
- resourceBase: RESOURCE_BASE,
26
- rank: BUNDLED_SKILL_RANK,
27
- locator: SKILL_URL,
28
- }
29
-
30
- export function formatUpdateNotice({ currentVersion, latestVersion, changelog }) {
31
- return [
32
- `> **Plugin update available**: tabbit-browser ${latestVersion} (installed ${currentVersion}).`,
33
- changelog ? `> New in ${latestVersion}: ${changelog}` : null,
34
- '> Show the offered version and these changes to the user, then ask whether to update now.',
35
- '> If they agree, tell them to rerun the install command below over the current install and restart the DSH session afterwards:',
36
- '> ```bash',
37
- '> dsh plugin --profile web add dsh-tabbit',
38
- '> ```',
39
- `> If they decline, call \`tabbit_plugin_update\` with \`dismiss: "${latestVersion}"\`, then continue the task.`,
40
- ]
41
- .filter(Boolean)
42
- .join('\n')
43
- }
44
-
45
- async function prependUpdateNotice(content, checkUpdate) {
46
- let update
47
- try {
48
- update = await checkUpdate()
49
- } catch {
50
- return content
51
- }
52
- if (update?.status !== 'update-available') return content
53
- return `${formatUpdateNotice(update)}\n\n${content}`
54
- }
55
-
56
- export function createSkillProvider({ checkUpdate = checkPluginUpdate } = {}) {
57
- return {
58
- name: PROVIDER_NAME,
59
- list: () => Promise.resolve([candidate]),
60
- async get(selected) {
61
- if (selected.name !== SKILL_NAME) return undefined
62
- const source = await readFile(SKILL_URL, 'utf8')
63
- return {
64
- name: SKILL_NAME,
65
- description: SKILL_DESCRIPTION,
66
- invocation: INVOCATION,
67
- provider: PROVIDER_NAME,
68
- source: 'bundled',
69
- resourceBase: RESOURCE_BASE,
70
- path: fileURLToPath(SKILL_URL),
71
- content: await prependUpdateNotice(stripFrontmatter(source), checkUpdate),
72
- }
73
- },
74
- }
75
- }
76
-
77
- function stripFrontmatter(source) {
78
- if (!source.startsWith('---\n')) return source
79
- const end = source.indexOf('\n---\n', 4)
80
- return end === -1 ? source : source.slice(end + 5)
81
- }
82
-
83
- export const name = 'tabbit-browser'
84
- export const inject = ['skills', 'tools', 'jobs']
85
-
86
- export function describeCliSandbox(platform = process.platform) {
87
- if (platform === 'win32') {
88
- return {
89
- cliSandboxMode: 'default',
90
- cliSandboxReason: 'Invoke tabbit-cli normally. Only if its Runtime connection probe returns BROWSER_RUNTIME_UNAVAILABLE while Tabbit Browser and the Runtime process are detected, ask the user to change the current DSH session to Full Permission and stop the task.',
91
- }
92
- }
93
- return {
94
- cliSandboxMode: 'default',
95
- cliSandboxReason: 'The default DSH sandbox mode can invoke tabbit-cli on this platform.',
96
- }
97
- }
98
-
99
- function withCliSandboxGuidance(message, platform) {
100
- const diagnosis = describeCliSandbox(platform)
101
- return {
102
- ...diagnosis,
103
- message,
104
- }
105
- }
106
-
107
- export function apply(ctx, options = {}) {
108
- ctx.skills.registerProvider(() => createSkillProvider(options))
109
- registerInstallerTool(ctx)
110
- registerUpdateTool(ctx, options)
111
- }
112
-
113
- function messageForUpdate(update) {
114
- if (update.status === 'update-available') {
115
- const changes = update.changelog || 'see the release notes'
116
- return `tabbit-browser ${update.latestVersion} is available (installed ${update.currentVersion}). New in this version: ${changes}. Ask the user whether to update now.`
117
- }
118
- if (update.status === 'current') {
119
- return `The tabbit-browser plugin is up to date (${update.currentVersion}).`
120
- }
121
- return 'Could not determine the latest tabbit-browser plugin version. The check stays silent for a day before retrying.'
122
- }
123
-
124
- export function registerUpdateTool(ctx, {
125
- checkUpdate = checkPluginUpdate,
126
- dismiss = dismissUpdate,
127
- } = {}) {
128
- ctx.tools.register({
129
- name: 'tabbit_plugin_update',
130
- description: 'Record that the user declined an offered tabbit-browser plugin version, or force a recheck of the published changelog. The skill already loads an update notice automatically when a newer version exists; call this tool only after the user declines an offered version, or after a plugin update or connectivity change.',
131
- parameters: {
132
- type: 'object',
133
- properties: {
134
- dismiss: {
135
- type: 'string',
136
- description: 'The offered version the user declined. The skill stops announcing this version; a newer release is announced again.',
137
- },
138
- refresh: {
139
- type: 'boolean',
140
- description: 'Skip the daily cache and the failure backoff and check the latest release again.',
141
- },
142
- },
143
- additionalProperties: false,
144
- },
145
- output: {
146
- schema: {
147
- type: 'object',
148
- properties: {
149
- status: {
150
- type: 'string',
151
- enum: ['current', 'update-available', 'unknown', 'dismissed'],
152
- },
153
- message: { type: 'string' },
154
- currentVersion: { type: 'string' },
155
- latestVersion: { type: 'string' },
156
- changelog: { type: 'string' },
157
- dismissedVersion: { type: 'string' },
158
- },
159
- required: ['status', 'message'],
160
- additionalProperties: false,
161
- },
162
- render: (_args, value) => [{ type: 'text', text: value.message }],
163
- },
164
- isConcurrencySafe: () => true,
165
- async execute(args = {}) {
166
- if (args.dismiss) {
167
- await dismiss(args.dismiss)
168
- return {
169
- status: 'dismissed',
170
- message: `Recorded that the user declined tabbit-browser ${args.dismiss}. The skill will stop announcing this version; a newer release will be announced again.`,
171
- dismissedVersion: args.dismiss,
172
- }
173
- }
174
- const update = await checkUpdate({ force: args.refresh === true })
175
- return {
176
- status: update.status,
177
- message: messageForUpdate(update),
178
- currentVersion: update.currentVersion,
179
- ...(update.latestVersion ? { latestVersion: update.latestVersion } : {}),
180
- ...(update.changelog ? { changelog: update.changelog } : {}),
181
- }
182
- },
183
- })
184
- }
185
-
186
- export function registerInstallerTool(ctx, {
187
- detect = detectTabbit,
188
- hostPlatform = process.platform,
189
- } = {}) {
190
- const activeJobs = new WeakMap()
191
- const readyByOwner = new WeakMap()
192
-
193
- ctx.tools.register({
194
- name: 'tabbit_browser_install',
195
- description: 'Check stable Tabbit editions, require version 1.9.0 or newer, verify the tabbit-cli launcher and Runtime process, and diagnose how to perform the session-scoped CLI connection probe. A successful detection result is cached for the calling agent session; set refresh only after a Runtime/launcher failure or installation change. Download the region-appropriate installer in the background when Tabbit is missing or outdated; otherwise report when the browser must be restarted once.',
196
- parameters: {
197
- type: 'object',
198
- properties: {
199
- refresh: {
200
- type: 'boolean',
201
- description: 'Discard this agent session\'s cached ready result and run every environment check again. Use only after a Runtime/launcher failure or installation change.',
202
- },
203
- },
204
- additionalProperties: false,
205
- },
206
- output: {
207
- schema: {
208
- type: 'object',
209
- properties: {
210
- status: {
211
- type: 'string',
212
- enum: ['ready', 'restart-required', 'background'],
213
- },
214
- message: { type: 'string' },
215
- cached: { type: 'boolean' },
216
- jobId: { type: 'string' },
217
- cliReady: { type: 'boolean' },
218
- cliSandboxMode: {
219
- type: 'string',
220
- enum: ['default', 'danger-full-access'],
221
- },
222
- cliSandboxReason: { type: 'string' },
223
- minimumVersion: { type: 'string' },
224
- playwrightProcessRunning: { type: 'boolean' },
225
- playwrightInstanceCount: { type: 'integer' },
226
- playwrightRuntimeAmbiguous: { type: 'boolean' },
227
- installations: {
228
- type: 'array',
229
- items: {
230
- type: 'object',
231
- properties: {
232
- name: { type: 'string' },
233
- edition: { type: 'string', enum: ['international', 'domestic'] },
234
- channel: { type: 'string', const: 'stable' },
235
- path: { type: 'string' },
236
- executable: { type: 'string' },
237
- version: { type: 'string' },
238
- bundleId: { type: 'string' },
239
- registryKey: { type: 'string' },
240
- },
241
- required: ['name', 'edition', 'channel'],
242
- additionalProperties: false,
243
- },
244
- },
245
- },
246
- required: ['status', 'message', 'cached', 'cliSandboxMode', 'cliSandboxReason'],
247
- additionalProperties: false,
248
- },
249
- render: (_args, value) => [{ type: 'text', text: value.message }],
250
- },
251
- isConcurrencySafe: () => true,
252
- async execute(args = {}, exec) {
253
- const owner = exec.agent
254
- if (owner && args.refresh === true) readyByOwner.delete(owner)
255
- if (owner && args.refresh !== true) {
256
- const cached = readyByOwner.get(owner)
257
- if (cached) {
258
- return {
259
- ...cached,
260
- cached: true,
261
- message: 'Browser installation and Runtime-process detection reused this session\'s result. Run the normal tabbit-cli tasks connection probe to finish the environment check.',
262
- }
263
- }
264
- }
265
- const existingJobId = owner ? activeJobs.get(owner) : undefined
266
- if (existingJobId) {
267
- try {
268
- const snapshot = ctx.jobs.get(existingJobId, owner)
269
- if (snapshot.status === 'running' || snapshot.status === 'stopping') {
270
- return {
271
- status: 'background',
272
- ...withCliSandboxGuidance(`Environment check failed: a Tabbit installer download is already running as ${existingJobId}.`, hostPlatform),
273
- cached: false,
274
- jobId: String(existingJobId),
275
- }
276
- }
277
- } catch {
278
- activeJobs.delete(owner)
279
- }
280
- }
281
-
282
- const detected = await detect()
283
- if (detected.recommendation === 'ready') {
284
- const versions = detected.supportedInstallations
285
- .map(item => `${item.name} ${item.version}`)
286
- .join(', ')
287
- const instanceNote = detected.playwrightRuntimeAmbiguous
288
- ? ` Multiple Tabbit instances are running (${detected.playwrightInstanceCount}); set TABBIT_PLAYWRIGHT_INSTANCE before invoking tabbit-cli.`
289
- : ''
290
- const result = {
291
- status: 'ready',
292
- ...withCliSandboxGuidance(`Browser installation and Runtime-process detection passed${versions ? ` (${versions})` : ''}.${instanceNote} Run the normal tabbit-cli tasks connection probe to finish the environment check.`, detected.platform),
293
- cached: false,
294
- cliReady: detected.cliReady,
295
- minimumVersion: detected.minimumVersion,
296
- playwrightProcessRunning: detected.playwrightProcessRunning,
297
- playwrightInstanceCount: detected.playwrightInstanceCount,
298
- playwrightRuntimeAmbiguous: detected.playwrightRuntimeAmbiguous,
299
- installations: detected.installations,
300
- }
301
- if (owner) readyByOwner.set(owner, result)
302
- return result
303
- }
304
- if (detected.recommendation === 'restart-required') {
305
- const versions = detected.supportedInstallations
306
- .map(item => `${item.name} ${item.version}`)
307
- .join(', ')
308
- return {
309
- status: 'restart-required',
310
- ...withCliSandboxGuidance(`Environment check failed: ${versions} meets the minimum version ${detected.minimumVersion}, but the tabbit-cli Runtime is not running. Please restart Tabbit Browser once before using browser automation.`, detected.platform),
311
- cached: false,
312
- cliReady: detected.cliReady,
313
- minimumVersion: detected.minimumVersion,
314
- playwrightProcessRunning: false,
315
- playwrightInstanceCount: detected.playwrightInstanceCount,
316
- playwrightRuntimeAmbiguous: detected.playwrightRuntimeAmbiguous,
317
- installations: detected.installations,
318
- }
319
- }
320
-
321
- const downloadReason = detected.installations.length === 0
322
- ? 'No stable Tabbit edition is installed.'
323
- : `Installed stable Tabbit version(s) do not meet the minimum ${detected.minimumVersion}: ${detected.installations.map(item => `${item.name} ${item.version ?? 'unknown'}`).join(', ')}.`
324
-
325
- let jobId
326
- jobId = ctx.jobs.start({
327
- kind: 'tabbit-installer',
328
- label: 'Download the region-appropriate Tabbit installer',
329
- outputLimitBytes: 16 * 1024,
330
- owner,
331
- run: () => createDownloadJob({
332
- onSettled: () => {
333
- if (owner && activeJobs.get(owner) === jobId) activeJobs.delete(owner)
334
- },
335
- }),
336
- })
337
- if (owner) activeJobs.set(owner, jobId)
338
- return {
339
- status: 'background',
340
- ...withCliSandboxGuidance(`Environment check failed: ${downloadReason} Started the region-appropriate Tabbit installer download as ${jobId}. DSH will report progress and notify you when the installer is ready.`, detected.platform),
341
- cached: false,
342
- jobId: String(jobId),
343
- cliReady: detected.cliReady,
344
- minimumVersion: detected.minimumVersion,
345
- playwrightProcessRunning: false,
346
- playwrightInstanceCount: detected.playwrightInstanceCount,
347
- playwrightRuntimeAmbiguous: detected.playwrightRuntimeAmbiguous,
348
- installations: detected.installations,
349
- }
350
- },
351
- })
352
- }