@anweat/dsh-browser 0.2.1 → 0.2.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 (169) hide show
  1. package/README.md +267 -69
  2. package/assets/skills/dsh-browser/SKILL.md +13 -11
  3. package/assets/skills/dsh-browser/references/files.md +36 -0
  4. package/assets/skills/dsh-browser/references/observe.md +2 -2
  5. package/assets/skills/dsh-browser/references/policy.md +32 -0
  6. package/cordis.patch.yml +10 -3
  7. package/lib/actions/errors.js +7 -2
  8. package/lib/actions/errors.js.map +1 -1
  9. package/lib/actions/index-view.d.ts +4 -1
  10. package/lib/actions/index-view.js +16 -3
  11. package/lib/actions/index-view.js.map +1 -1
  12. package/lib/actions/inspect.js +15 -0
  13. package/lib/actions/inspect.js.map +1 -1
  14. package/lib/actions/observe.js +1 -1
  15. package/lib/actions/observe.js.map +1 -1
  16. package/lib/actions/opencli.d.ts +1 -1
  17. package/lib/actions/opencli.js +4 -4
  18. package/lib/actions/opencli.js.map +1 -1
  19. package/lib/actions/policy.d.ts +15 -0
  20. package/lib/actions/policy.js +131 -0
  21. package/lib/actions/policy.js.map +1 -0
  22. package/lib/actions/registry.js +6 -5
  23. package/lib/actions/registry.js.map +1 -1
  24. package/lib/actions/runtime.js +2 -2
  25. package/lib/actions/runtime.js.map +1 -1
  26. package/lib/actions/target.js +35 -3
  27. package/lib/actions/target.js.map +1 -1
  28. package/lib/actions/types.d.ts +1 -1
  29. package/lib/actions/types.js.map +1 -1
  30. package/lib/approval-policy.d.ts +10 -1
  31. package/lib/approval-policy.js +42 -1
  32. package/lib/approval-policy.js.map +1 -1
  33. package/lib/auth-profiles.d.ts +4 -1
  34. package/lib/auth-profiles.js +3 -0
  35. package/lib/auth-profiles.js.map +1 -1
  36. package/lib/automation-assets-rpc.js +26 -0
  37. package/lib/automation-assets-rpc.js.map +1 -1
  38. package/lib/browser-service.d.ts +165 -4
  39. package/lib/browser-service.js +505 -55
  40. package/lib/browser-service.js.map +1 -1
  41. package/lib/client/SettingsCard.d.ts +10 -8
  42. package/lib/client/SettingsCard.js +12 -137
  43. package/lib/client/SettingsCard.js.map +1 -1
  44. package/lib/client/auth-profiles-form.d.ts +23 -0
  45. package/lib/client/auth-profiles-form.js +64 -0
  46. package/lib/client/auth-profiles-form.js.map +1 -0
  47. package/lib/client/card-frame.d.ts +22 -0
  48. package/lib/client/card-frame.js +61 -0
  49. package/lib/client/card-frame.js.map +1 -0
  50. package/lib/client/field-rows.d.ts +59 -0
  51. package/lib/client/field-rows.js +63 -0
  52. package/lib/client/field-rows.js.map +1 -0
  53. package/lib/client/field-rules.d.ts +17 -0
  54. package/lib/client/field-rules.js +68 -0
  55. package/lib/client/field-rules.js.map +1 -0
  56. package/lib/client/form.d.ts +108 -7
  57. package/lib/client/form.js +255 -11
  58. package/lib/client/form.js.map +1 -1
  59. package/lib/client/group-access.d.ts +14 -0
  60. package/lib/client/group-access.js +97 -0
  61. package/lib/client/group-access.js.map +1 -0
  62. package/lib/client/group-advanced.d.ts +11 -0
  63. package/lib/client/group-advanced.js +32 -0
  64. package/lib/client/group-advanced.js.map +1 -0
  65. package/lib/client/group-assets.d.ts +13 -0
  66. package/lib/client/group-assets.js +50 -0
  67. package/lib/client/group-assets.js.map +1 -0
  68. package/lib/client/group-browser.d.ts +9 -0
  69. package/lib/client/group-browser.js +27 -0
  70. package/lib/client/group-browser.js.map +1 -0
  71. package/lib/client/group-common.d.ts +10 -0
  72. package/lib/client/group-common.js +21 -0
  73. package/lib/client/group-common.js.map +1 -0
  74. package/lib/client/group-context.d.ts +20 -0
  75. package/lib/client/group-context.js +76 -0
  76. package/lib/client/group-context.js.map +1 -0
  77. package/lib/client/group-downloads.d.ts +10 -0
  78. package/lib/client/group-downloads.js +33 -0
  79. package/lib/client/group-downloads.js.map +1 -0
  80. package/lib/client/group-limits.d.ts +9 -0
  81. package/lib/client/group-limits.js +57 -0
  82. package/lib/client/group-limits.js.map +1 -0
  83. package/lib/client/group-prompts.d.ts +13 -0
  84. package/lib/client/group-prompts.js +136 -0
  85. package/lib/client/group-prompts.js.map +1 -0
  86. package/lib/client/index.d.ts +21 -1
  87. package/lib/client/index.js +10 -4
  88. package/lib/client/index.js.map +1 -1
  89. package/lib/client/locales-policy.d.ts +83 -0
  90. package/lib/client/locales-policy.js +46 -0
  91. package/lib/client/locales-policy.js.map +1 -0
  92. package/lib/client/locales-prompts.d.ts +57 -0
  93. package/lib/client/locales-prompts.js +41 -0
  94. package/lib/client/locales-prompts.js.map +1 -0
  95. package/lib/client/locales-ui.d.ts +223 -0
  96. package/lib/client/locales-ui.js +157 -0
  97. package/lib/client/locales-ui.js.map +1 -0
  98. package/lib/client/locales.d.ts +359 -67
  99. package/lib/client/locales.js +68 -22
  100. package/lib/client/locales.js.map +1 -1
  101. package/lib/client/policy-fields.d.ts +31 -0
  102. package/lib/client/policy-fields.js +66 -0
  103. package/lib/client/policy-fields.js.map +1 -0
  104. package/lib/client/policy-status-client.d.ts +91 -0
  105. package/lib/client/policy-status-client.js +64 -0
  106. package/lib/client/policy-status-client.js.map +1 -0
  107. package/lib/client/prompts-form.d.ts +33 -0
  108. package/lib/client/prompts-form.js +87 -0
  109. package/lib/client/prompts-form.js.map +1 -1
  110. package/lib/client/prompts-status-client.d.ts +21 -1
  111. package/lib/client/prompts-status-client.js +36 -4
  112. package/lib/client/prompts-status-client.js.map +1 -1
  113. package/lib/client/properties-form.d.ts +90 -0
  114. package/lib/client/properties-form.js +187 -0
  115. package/lib/client/properties-form.js.map +1 -0
  116. package/lib/client/property-docs.d.ts +18 -0
  117. package/lib/client/property-docs.js +78 -0
  118. package/lib/client/property-docs.js.map +1 -0
  119. package/lib/client/size-units.d.ts +12 -0
  120. package/lib/client/size-units.js +29 -0
  121. package/lib/client/size-units.js.map +1 -0
  122. package/lib/client/styles.d.ts +47 -31
  123. package/lib/client/styles.js +59 -20
  124. package/lib/client/styles.js.map +1 -1
  125. package/lib/client/ui-kit.d.ts +173 -0
  126. package/lib/client/ui-kit.js +112 -0
  127. package/lib/client/ui-kit.js.map +1 -0
  128. package/lib/client.js +5291 -834
  129. package/lib/client.js.map +1 -1
  130. package/lib/config-rules.d.ts +40 -0
  131. package/lib/config-rules.js +103 -0
  132. package/lib/config-rules.js.map +1 -0
  133. package/lib/config.d.ts +64 -2
  134. package/lib/config.js +106 -0
  135. package/lib/config.js.map +1 -1
  136. package/lib/context-options.d.ts +85 -0
  137. package/lib/context-options.js +206 -0
  138. package/lib/context-options.js.map +1 -0
  139. package/lib/deps.d.ts +42 -5
  140. package/lib/deps.js +219 -9
  141. package/lib/deps.js.map +1 -1
  142. package/lib/downloads.d.ts +79 -0
  143. package/lib/downloads.js +189 -0
  144. package/lib/downloads.js.map +1 -0
  145. package/lib/index.d.ts +3 -2
  146. package/lib/index.js +7 -3
  147. package/lib/index.js.map +1 -1
  148. package/lib/policy-keys.d.ts +39 -0
  149. package/lib/policy-keys.js +21 -0
  150. package/lib/policy-keys.js.map +1 -1
  151. package/lib/policy-overlay.d.ts +189 -0
  152. package/lib/policy-overlay.js +649 -0
  153. package/lib/policy-overlay.js.map +1 -0
  154. package/lib/prompts-yaml.d.ts +11 -0
  155. package/lib/prompts-yaml.js +121 -0
  156. package/lib/prompts-yaml.js.map +1 -0
  157. package/lib/prompts.d.ts +2 -0
  158. package/lib/prompts.js +7 -3
  159. package/lib/prompts.js.map +1 -1
  160. package/lib/system-browser.d.ts +61 -0
  161. package/lib/system-browser.js +109 -0
  162. package/lib/system-browser.js.map +1 -0
  163. package/lib/tools.js +9 -1
  164. package/lib/tools.js.map +1 -1
  165. package/lib/usage-policy.d.ts +3 -1
  166. package/lib/usage-policy.js +2 -0
  167. package/lib/usage-policy.js.map +1 -1
  168. package/package.json +42 -38
  169. package/scripts/dump-prompts.mjs +12 -2
@@ -8,7 +8,7 @@
8
8
  * - render / snapshot / searchResults are the drop-in replacements for
9
9
  * web-search-pro's former PlaywrightManager (rules-aware page extraction and
10
10
  * platform search-page list extraction run in the page itself).
11
- * - opencli(...) runs the bundled @jackwener/opencli (no global CLI).
11
+ * - opencli(...) runs @jackwener/opencli when it is installed (optional, see deps.ts).
12
12
  * - The interactive surface (open/click/type/hover/setFiles/evaluate/scroll/read/screenshot/closePage)
13
13
  * drives ONE persistent context+page, giving the model multi-step browsing.
14
14
  *
@@ -18,7 +18,8 @@
18
18
  * doubled (\\s) so the evaluated page code sees a correct \s.
19
19
  * @module dsh-browser/browser-service
20
20
  */
21
- import { type CliResult } from './deps.ts';
21
+ import { type CliResult, type OpencliSource } from './deps.ts';
22
+ import { type BrowserPlan, type SystemBrowser } from './system-browser.ts';
22
23
  import type { ResolvedConfig } from './config.ts';
23
24
  import { type ResolvedAuthProfile } from './auth-profiles.ts';
24
25
  import { type ResolvedRulePack } from './rule-packs.ts';
@@ -28,9 +29,12 @@ import { type UserscriptValidation } from './scripts.ts';
28
29
  import { type AutomationMode } from './freedom.ts';
29
30
  import { type OpencliCatalogFilter, type OpencliCatalogItem } from './opencli-catalog.ts';
30
31
  import { UsageGovernor } from './usage-policy.ts';
32
+ import { type AuditEntry, type ChangeMeta, type Direction, type PolicyChange, type PolicyDiff } from './policy-overlay.ts';
31
33
  import { type BrowserTarget } from './locator.ts';
32
34
  import { SessionJournal } from './journal.ts';
33
35
  import { type ObserveRegion, type ObserveSection } from './observe.ts';
36
+ import { type CallContextInput, type ContextOptions } from './context-options.ts';
37
+ import { type DownloadRecord } from './downloads.ts';
34
38
  export interface RenderRule {
35
39
  hostname: string;
36
40
  contentSelectors: string[];
@@ -76,6 +80,15 @@ export interface InteractiveState {
76
80
  screenshotPath?: string;
77
81
  /** The page the action ran on closed itself (a popup's own close button): url/title/text describe the page the session fell back to, or are empty. */
78
82
  closedPage?: true;
83
+ /** target.open only: the context options in effect, when the call set any or the context was rebuilt. */
84
+ contextOptions?: Record<string, unknown>;
85
+ /** target.open only: where each value of `contextOptions` comes from: `call` (this call), `session` (an earlier call kept by the context), `default` (configuration). */
86
+ contextSources?: Record<string, 'call' | 'session' | 'default'>;
87
+ /** target.open only: the session's context was replaced because the call needed different options (or login state); its pages are gone. */
88
+ contextRebuilt?: {
89
+ changed: string[];
90
+ note: string;
91
+ };
79
92
  }
80
93
  /** The page state after a recipe, plus what the run did ({@link RecipeRunResult}). */
81
94
  export interface RecipeServiceResult extends InteractiveState, RecipeRunResult {
@@ -100,6 +113,22 @@ export interface EvaluateResult {
100
113
  export interface FileUploadResult extends InteractiveState {
101
114
  files: string[];
102
115
  }
116
+ /** The outcome of `target.download`: where the file is and what it is. */
117
+ export interface DownloadResult {
118
+ id: string;
119
+ /** The URL that was asked for. */
120
+ url: string;
121
+ /** The URL the bytes finally came from (after redirects), when it differs. */
122
+ finalUrl?: string;
123
+ /** Absolute path of the saved file, inside `downloadDir`. */
124
+ path: string;
125
+ /** The saved file's name (may carry a `-1` style suffix when the name was taken). */
126
+ filename: string;
127
+ bytes: number;
128
+ sha256: string;
129
+ suggestedFilename: string;
130
+ source: 'request' | 'browser';
131
+ }
103
132
  export type { BrowserFrameSpec, BrowserLocatorSpec, BrowserTarget } from './locator.ts';
104
133
  export interface BrowserConsoleRecord {
105
134
  type: string;
@@ -160,6 +189,10 @@ export interface BrowserStatus {
160
189
  opencliEnabled: boolean;
161
190
  opencliInstalled: boolean;
162
191
  opencliEntryPath?: string;
192
+ /** Where OpenCLI was found (`plugin`, `path`, `global-npm`, ...). Absent when it is not installed. */
193
+ opencliSource?: OpencliSource;
194
+ /** Present while OpenCLI is enabled but not installed: how to install it. OpenCLI is optional; this is not a warning. */
195
+ opencliInstallHint?: string;
163
196
  automationMode: AutomationMode;
164
197
  /** Tool names registered for the model under the configured toolSurface. */
165
198
  exposedTools: string[];
@@ -170,9 +203,32 @@ export interface BrowserStatus {
170
203
  externalUserscriptPolicy: 'deny' | 'ask' | 'allow';
171
204
  pageEvaluatePolicy: 'deny' | 'ask' | 'allow';
172
205
  fileUploadPolicy: 'deny' | 'ask' | 'allow';
206
+ downloadPolicy: 'deny' | 'ask' | 'allow';
207
+ /** Directory downloads are saved to, and the size cap of one download. */
208
+ downloadDir: string;
209
+ downloadMaxBytes: number;
210
+ /** Context options applied to every context (viewport, user agent, locale, time zone, proxy server); only the ones that are set. */
211
+ contextDefaults: Record<string, unknown>;
173
212
  opencliRunPolicy: 'deny' | 'ask' | 'allow';
174
213
  chromiumInstalled: boolean;
175
214
  chromiumExecutablePath?: string;
215
+ /**
216
+ * The browser a launch will use, decided without launching one. `ready: false` means nothing can be launched yet and
217
+ * `installAdvice` says what to do; `source: system-browser` means the Playwright Chromium is missing and Chrome / Edge is used instead.
218
+ */
219
+ browser: {
220
+ ready: boolean;
221
+ source: BrowserPlan['source'];
222
+ path?: string;
223
+ note?: string;
224
+ installAdvice?: string;
225
+ };
226
+ /** System Chrome / Edge found on this machine (what the automatic fallback can use). */
227
+ systemBrowsers: {
228
+ channel: string;
229
+ name: string;
230
+ path: string;
231
+ }[];
176
232
  usagePolicy: ResolvedConfig['usagePolicy'];
177
233
  usageGovernor: ReturnType<UsageGovernor['snapshot']>;
178
234
  authProfiles: {
@@ -222,6 +278,16 @@ interface SessionState {
222
278
  generationClock: number;
223
279
  profile?: ResolvedAuthProfile;
224
280
  rulePack?: ResolvedRulePack;
281
+ /** Creation options of the live context (viewport, user agent, locale, time zone, proxy); compared on the next target.open. */
282
+ contextOptions?: ContextOptions;
283
+ /** Downloads this session saved or failed to save, newest last, capped. Memory only; the files are on disk. */
284
+ downloads: DownloadRecord[];
285
+ /** Counter behind the `d1`, `d2`, ... download ids of this session. */
286
+ nextDownload: number;
287
+ /** Save jobs of browser download events, so target.download can wait for the one its navigation triggered. */
288
+ downloadJobs: WeakMap<object, Promise<DownloadRecord>>;
289
+ /** Filename a running target.download wants for the download its navigation triggers (consumed by that one event). */
290
+ downloadFilename?: string;
225
291
  captureConsole: boolean;
226
292
  captureNetwork: boolean;
227
293
  capturedConsole: BrowserConsoleRecord[];
@@ -268,6 +334,7 @@ export interface SessionIdentity {
268
334
  }
269
335
  export declare class BrowserService {
270
336
  private readonly config;
337
+ private readonly probe;
271
338
  private browser;
272
339
  private launching?;
273
340
  /** Per-session interactive state. Replaces the former global activePage. */
@@ -277,13 +344,53 @@ export declare class BrowserService {
277
344
  private clock;
278
345
  private readonly authProfiles;
279
346
  private readonly usageGovernor;
347
+ /** The managed policy overlay merged over the configured authProfiles and usagePolicy (see policy-overlay.ts). */
348
+ private readonly policyStore;
280
349
  private opencliCatalogCache?;
281
350
  /** Per-session exploration journals (see src/journal.ts). Released with the session, never written to disk. */
282
351
  private readonly journals;
283
352
  /** How many sessions may hold a context+page at once before LRU eviction. */
284
353
  private readonly sessionLimit;
285
- constructor(config: ResolvedConfig);
354
+ /**
355
+ * `probe` replaces what the service reads from the machine (system browsers, file existence); tests use it, the
356
+ * plugin never passes it.
357
+ */
358
+ constructor(config: ResolvedConfig, probe?: {
359
+ systemBrowsers?: () => SystemBrowser[];
360
+ exists?: (file: string) => boolean;
361
+ });
286
362
  available(): boolean;
363
+ /** Pick up an overlay file that changed on disk (another process, the panel). Cheap: one stat. */
364
+ private refreshPolicy;
365
+ private applyEffectivePolicy;
366
+ /** What a policy change would do, without doing it; `ok: false` carries why it is invalid. */
367
+ previewPolicyChange(change: PolicyChange): {
368
+ ok: true;
369
+ direction: Direction;
370
+ lines: string[];
371
+ } | {
372
+ ok: false;
373
+ message: string;
374
+ };
375
+ /**
376
+ * Apply a policy change: validated, written atomically with an audit entry, effective at once. Sessions whose
377
+ * context holds a login state this change took away are closed, so a revoked domain stops working immediately
378
+ * instead of at the next page load.
379
+ */
380
+ changePolicy(change: PolicyChange, meta: ChangeMeta): Promise<{
381
+ revision: number;
382
+ direction: Direction;
383
+ diff: PolicyDiff;
384
+ audit: AuditEntry;
385
+ sessionsClosed: number;
386
+ before: Record<string, unknown>;
387
+ after: Record<string, unknown>;
388
+ }>;
389
+ /** The effective policy, where each value comes from, and the newest audit entries. */
390
+ policyReport(opts?: {
391
+ auditLimit?: number;
392
+ viewerSession?: string;
393
+ }): Record<string, unknown>;
287
394
  /**
288
395
  * This session's exploration journal, created on first use. It outlives `target.close` (the session is
289
396
  * still the same) and is released when the session is evicted or the service closes. A session that never
@@ -339,9 +446,24 @@ export declare class BrowserService {
339
446
  private handleBrowserDisconnected;
340
447
  private trackBrowser;
341
448
  private ensure;
449
+ private systemBrowsers;
450
+ /** What a launch would use right now. `pw` is the loaded runtime; without it (not installed) the Playwright Chromium path is unknown. */
451
+ private browserPlan;
452
+ /** The message for a launch that found no browser. Says which setting was wrong when the user chose one. */
453
+ private launchFailure;
342
454
  /** Run `playwright install chromium` from the bundled playwright CLI. */
343
455
  installChromium(): Promise<CliResult>;
344
456
  private navigate;
457
+ /**
458
+ * `newContext()` arguments that do not depend on the login state: the configured viewport, user agent, locale,
459
+ * time zone and proxy (or the per-call override that replaced them), plus downloads for interactive contexts.
460
+ *
461
+ * Proxy: Playwright applies `proxy` at context level on its own. Measured on Chromium 1243 (headless shell) with
462
+ * Playwright 1.62 and Patchright 1.62, a context created with `proxy` and a browser launched WITHOUT any launch
463
+ * `proxy` sent both page navigation and `context.request` through the proxy, so no `proxy: { server: 'per-context' }`
464
+ * launch option is set. (Chromium bypasses the proxy for loopback hosts regardless; that is the browser's rule.)
465
+ */
466
+ private contextCreationOptions;
345
467
  private transientContext;
346
468
  private persistAndClose;
347
469
  render(url: string, rules: readonly RenderRule[], opts?: {
@@ -415,6 +537,13 @@ export declare class BrowserService {
415
537
  * or mutating another's page, cookies, console or network log.
416
538
  */
417
539
  private ensureActivePage;
540
+ /**
541
+ * {@link ensureActivePage} plus what it had to do to the session's context. `context` carries the per-call creation
542
+ * options of `target.open`: an option the call leaves out keeps what the live context has (a context is only
543
+ * rebuilt for a value that is different, never because a later call said nothing), and a new context starts from
544
+ * the configured defaults. `rebuilt` names what differed when a live page was closed to make room for the new context.
545
+ */
546
+ private ensurePage;
418
547
  /**
419
548
  * Add a page to the session's list and wire its capture and close handling.
420
549
  * Idempotent: the context 'page' event and the explicit call both land here.
@@ -443,7 +572,7 @@ export declare class BrowserService {
443
572
  rulePack?: string;
444
573
  capture?: readonly ('console' | 'network')[];
445
574
  session?: string;
446
- }): Promise<InteractiveState>;
575
+ } & CallContextInput): Promise<InteractiveState>;
447
576
  click(target: BrowserTarget, opts?: {
448
577
  expectGeneration?: number;
449
578
  timeoutMs?: number;
@@ -557,6 +686,38 @@ export declare class BrowserService {
557
686
  session?: string;
558
687
  }): Promise<Record<string, unknown>>;
559
688
  private observeOnce;
689
+ private pushDownload;
690
+ private failedDownload;
691
+ /**
692
+ * Save one browser download event (navigation to an attachment, a click, a script-created link) into the
693
+ * download directory and record the outcome in the session that owns the page. Never rejects: a failure is a
694
+ * `failed` record with the partial file removed, so nothing half-written is ever reported as a result.
695
+ */
696
+ private saveBrowserDownload;
697
+ /**
698
+ * `target.download`: save a URL into the download directory.
699
+ *
700
+ * First an HTTP request made through the session's own context (its cookies and login state, its proxy and
701
+ * user agent); if that does not produce bytes, the URL is opened in a throwaway page of the same context and the
702
+ * attachment download that navigation triggers is captured. A request that returns nothing is an error, never a
703
+ * zero-byte file. The session's own page is not navigated.
704
+ */
705
+ download(url: string, opts?: {
706
+ filename?: string;
707
+ timeoutMs?: number;
708
+ signal?: AbortSignal;
709
+ session?: string;
710
+ }): Promise<DownloadResult>;
711
+ /** `inspect.downloads`: this session's download records (newest last); reading never creates a session bucket. */
712
+ downloadRecords(opts?: {
713
+ limit?: number;
714
+ clear?: boolean;
715
+ session?: string;
716
+ }): {
717
+ downloadDir: string;
718
+ records: DownloadRecord[];
719
+ total: number;
720
+ };
560
721
  consoleMessages(opts?: {
561
722
  level?: string;
562
723
  limit?: number;