dsh-builtin-browser 0.1.8 → 0.1.9
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/lib/browser/runtime.d.ts +2 -1
- package/lib/browser/runtime.js +15 -2
- package/lib/browser/types.d.ts +2 -1
- package/lib/browser-electron/entry.js +6 -1
- package/lib/browser-electron/host-main.js +26 -2
- package/lib/browser-electron/provider.js +50 -20
- package/lib/browser-electron/remote-host.d.ts +7 -4
- package/lib/browser-electron/remote-host.js +65 -15
- package/lib/index.d.ts +8 -7
- package/lib/index.js +7 -6
- package/lib/tool-browser/index.d.ts +1 -1
- package/lib/tool-browser/index.js +44 -20
- package/package.json +1 -1
- package/src/browser/runtime.ts +14 -2
- package/src/browser/types.ts +2 -1
- package/src/browser-electron/entry.ts +6 -1
- package/src/browser-electron/host-main.ts +22 -2
- package/src/browser-electron/provider.ts +67 -21
- package/src/browser-electron/remote-host.ts +62 -12
- package/src/index.ts +12 -6
- package/src/tool-browser/index.ts +44 -21
|
@@ -36,6 +36,8 @@ export interface Config {
|
|
|
36
36
|
|
|
37
37
|
/** Per-task browser sessions, keyed by the calling DSH session id. */
|
|
38
38
|
const sessionsByTask = new Map<string, BrowserSessionId>()
|
|
39
|
+
/** In-flight first-open per task key, so concurrent first calls share one session. */
|
|
40
|
+
const pendingOpens = new Map<string, Promise<BrowserSessionId>>()
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* Action restriction state: an allow-list of browser tool names, or undefined
|
|
@@ -66,24 +68,29 @@ function taskKey(exec: { agent?: { id?: string } } | undefined): string {
|
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
70
|
* Resolve the calling task's browser session, opening one on first use.
|
|
71
|
+
* Concurrent first calls for the same key share a single open.
|
|
69
72
|
* @param browser - the seam service.
|
|
70
73
|
* @param key - the task key (see {@link taskKey}).
|
|
71
74
|
* @returns the task's session id.
|
|
72
75
|
*/
|
|
73
76
|
async function ensureSession(browser: NonNullable<Context['browser']>, key: string): Promise<BrowserSessionId> {
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
const existing = sessionsByTask.get(key)
|
|
78
|
+
if (existing !== undefined) return existing
|
|
79
|
+
const pending = pendingOpens.get(key)
|
|
80
|
+
if (pending !== undefined) return pending
|
|
81
|
+
const opening = browser.open().then(
|
|
82
|
+
session => { sessionsByTask.set(key, session); pendingOpens.delete(key); return session },
|
|
83
|
+
error => { pendingOpens.delete(key); throw error },
|
|
84
|
+
)
|
|
85
|
+
pendingOpens.set(key, opening)
|
|
86
|
+
return opening
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
/** Coerce a tool-provided string value back to boolean/number when
|
|
89
|
+
/** Coerce a tool-provided string value back to boolean/number only when lossless. */
|
|
83
90
|
function parseFillValue(v: string | undefined): string | number | boolean {
|
|
84
91
|
if (v === 'true') return true
|
|
85
92
|
if (v === 'false') return false
|
|
86
|
-
if (v !== undefined && /^-?\d+(\.\d+)?$/.test(v)) return Number(v)
|
|
93
|
+
if (v !== undefined && /^-?\d+(\.\d+)?$/.test(v) && String(Number(v)) === v) return Number(v)
|
|
87
94
|
return v ?? ''
|
|
88
95
|
}
|
|
89
96
|
|
|
@@ -108,7 +115,8 @@ function formatSnapshot(snapshot: {
|
|
|
108
115
|
/** Register all browser tools with `ctx.tools`. */
|
|
109
116
|
export function apply(ctx: Context, config: Config = {}): void {
|
|
110
117
|
const timeoutMs = config.timeoutMs ?? 60_000
|
|
111
|
-
|
|
118
|
+
// Re-apply resets the restriction: an omitted allowedActions lifts it.
|
|
119
|
+
restrictedTo = config.allowedActions !== undefined ? [...config.allowedActions] : undefined
|
|
112
120
|
|
|
113
121
|
ctx.systemPrompt.section({
|
|
114
122
|
name: 'tool:browser',
|
|
@@ -132,6 +140,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
132
140
|
properties: {
|
|
133
141
|
url: { type: 'string', required: true },
|
|
134
142
|
title: { type: 'string' },
|
|
143
|
+
truncated: { type: 'boolean' },
|
|
135
144
|
elements: {
|
|
136
145
|
type: 'array',
|
|
137
146
|
required: true,
|
|
@@ -193,6 +202,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
193
202
|
properties: {
|
|
194
203
|
url: { type: 'string', required: true },
|
|
195
204
|
title: { type: 'string' },
|
|
205
|
+
truncated: { type: 'boolean' },
|
|
196
206
|
elements: {
|
|
197
207
|
type: 'array',
|
|
198
208
|
required: true,
|
|
@@ -571,6 +581,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
571
581
|
timeoutMs,
|
|
572
582
|
isConcurrencySafe: () => true,
|
|
573
583
|
async execute(args, exec) {
|
|
584
|
+
assertAllowed('browser_switch_tab')
|
|
574
585
|
const browser = ctx.get('browser')
|
|
575
586
|
if (browser === undefined) throw new Error('tool-browser: browser service unavailable')
|
|
576
587
|
const session = await ensureSession(browser, taskKey(exec))
|
|
@@ -653,9 +664,11 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
653
664
|
if (entries.length === 0) return [{ type: 'text', text: '(no recorded operations yet)' }]
|
|
654
665
|
return [{
|
|
655
666
|
type: 'text',
|
|
656
|
-
text: entries.map(e =>
|
|
657
|
-
|
|
658
|
-
|
|
667
|
+
text: entries.map(e => {
|
|
668
|
+
const rawParams = JSON.stringify(e.params)
|
|
669
|
+
const shownParams = rawParams.length > 300 ? rawParams.slice(0, 300) + '…' : rawParams
|
|
670
|
+
return `#${e.seq} ${e.action} ${e.ok ? 'ok' : 'FAIL'} ${shownParams}${e.result !== undefined ? ` -> ${e.result}` : ''}${e.error !== undefined ? ` !! ${e.error}` : ''}`
|
|
671
|
+
}).join('\n'),
|
|
659
672
|
}]
|
|
660
673
|
},
|
|
661
674
|
},
|
|
@@ -761,9 +774,7 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
761
774
|
async execute(_args, exec) {
|
|
762
775
|
const browser = ctx.get('browser')
|
|
763
776
|
if (browser === undefined) throw new Error('tool-browser: browser service unavailable')
|
|
764
|
-
const
|
|
765
|
-
const session = sessionsByTask.get(key) ?? await browser.open()
|
|
766
|
-
sessionsByTask.set(key, session)
|
|
777
|
+
const session = await ensureSession(browser, taskKey(exec))
|
|
767
778
|
const tabs = await browser.listTabs(session)
|
|
768
779
|
return { session, tabs: tabs.map(t => ({ id: t.id, url: t.url, active: t.active })) }
|
|
769
780
|
},
|
|
@@ -786,8 +797,13 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
786
797
|
const key = taskKey(exec)
|
|
787
798
|
const session = sessionsByTask.get(key)
|
|
788
799
|
if (session !== undefined) {
|
|
789
|
-
|
|
790
|
-
|
|
800
|
+
try {
|
|
801
|
+
await browser.close(session)
|
|
802
|
+
} finally {
|
|
803
|
+
// Always forget the mapping so the next call opens a fresh session,
|
|
804
|
+
// even if the provider close threw (the session is half-closed).
|
|
805
|
+
sessionsByTask.delete(key)
|
|
806
|
+
}
|
|
791
807
|
}
|
|
792
808
|
return { reset: true }
|
|
793
809
|
},
|
|
@@ -811,8 +827,15 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
811
827
|
isConcurrencySafe: () => false,
|
|
812
828
|
async execute(args, exec) {
|
|
813
829
|
// Always allowed so the guard can be lifted.
|
|
814
|
-
|
|
815
|
-
|
|
830
|
+
const allowed = args.allowed ?? []
|
|
831
|
+
const unknown = allowed.filter((t: string) => !t.startsWith('browser_'))
|
|
832
|
+
if (unknown.length > 0) {
|
|
833
|
+
throw new Error(`browser_restrict: unknown tool name(s) ${unknown.map(t => `"${t}"`).join(', ')} (must start with "browser_")`)
|
|
834
|
+
}
|
|
835
|
+
// Empty list (or omitted) lifts the restriction; a non-empty list is the
|
|
836
|
+
// new allow-list.
|
|
837
|
+
restrictedTo = allowed.length === 0 ? undefined : [...allowed]
|
|
838
|
+
return { restrictedTo: restrictedTo === undefined ? [] : [...restrictedTo] }
|
|
816
839
|
},
|
|
817
840
|
}))
|
|
818
841
|
|
|
@@ -854,8 +877,8 @@ export function apply(ctx: Context, config: Config = {}): void {
|
|
|
854
877
|
|
|
855
878
|
/** Test hook: inspect and reset the plugin-level session map (used by tests). */
|
|
856
879
|
export const internals = {
|
|
857
|
-
/**
|
|
858
|
-
get sessions(): ReadonlyMap<string, BrowserSessionId> { return sessionsByTask },
|
|
880
|
+
/** A copy of the per-task session map (task key -> provider session id). */
|
|
881
|
+
get sessions(): ReadonlyMap<string, BrowserSessionId> { return new Map(sessionsByTask) },
|
|
859
882
|
/** Drop one task's mapping without closing the provider session. */
|
|
860
883
|
clearSession(key = 'default'): void { sessionsByTask.delete(key) },
|
|
861
884
|
}
|