@logictan/dsh-browser-agent 0.1.1 → 0.2.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/cordis.patch.yml +3 -3
- package/lib/client.js +61 -23
- package/lib/config.js +42 -17
- package/lib/index.js +51 -16
- package/package.json +3 -9
package/cordis.patch.yml
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# dsh-browser-agent bundle patch layer.
|
|
2
2
|
#
|
|
3
3
|
# One bare plugin row. The package ships two halves — the host half
|
|
4
|
-
# (exports ".") registers the `
|
|
5
|
-
# `
|
|
6
|
-
# the Plugins-page configuration card.
|
|
4
|
+
# (exports ".") registers the `browser_agent` model tool and exports the
|
|
5
|
+
# `Config` schema the Plugins page derives its form from, the browser half
|
|
6
|
+
# (exports "./client") registers the Plugins-page configuration card.
|
|
7
7
|
#
|
|
8
8
|
# The row must stay a BARE package name: the web plugin table locates each
|
|
9
9
|
# package's `dsh.client` manifest from the specifier of the loader row that
|
package/lib/client.js
CHANGED
|
@@ -11,13 +11,29 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The field list mirrors the schema in `src/config.js`; the two are kept in
|
|
13
13
|
* step by hand because this half ships as plain browser code.
|
|
14
|
+
*
|
|
15
|
+
* As of the 0.1.7-alpha.1 settings redesign the settings service is
|
|
16
|
+
* `configForms`, not `settingsScope`: a form is addressed by loader ENTRY ID
|
|
17
|
+
* (`configForms.get(entryId)`) rather than bound by namespace, and the shared
|
|
18
|
+
* describe mirror is reached through `configForms.describe()`. The service is
|
|
19
|
+
* injected NESTED inside `apply` rather than declared in `inject`, so a host
|
|
20
|
+
* without the settings UI loses only this card instead of failing the page's
|
|
21
|
+
* boot audit.
|
|
14
22
|
*/
|
|
15
23
|
window.__ModuleLoader__.load({
|
|
16
24
|
id: "@logictan/dsh-browser-agent",
|
|
17
25
|
factory: function (require) {
|
|
18
26
|
var React = require("react");
|
|
19
27
|
|
|
20
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Loader entry id this card's form is keyed by.
|
|
30
|
+
*
|
|
31
|
+
* `configForms.get(entryId)` addresses the entry the Host serves, and
|
|
32
|
+
* `dsh-settings` keys that entry by profile entry id — which the repo
|
|
33
|
+
* convention pins to the host half's `export const name`. It is the same
|
|
34
|
+
* string the mirror rows carry as `ns`, which is how the secret sidecar
|
|
35
|
+
* below is found.
|
|
36
|
+
*/
|
|
21
37
|
var NS = "browser-agent";
|
|
22
38
|
/** The patch row this card configures (cordis.patch.yml). */
|
|
23
39
|
var ROW_ID = "browser-agent";
|
|
@@ -256,14 +272,17 @@ window.__ModuleLoader__.load({
|
|
|
256
272
|
* whether a key is set — overridden() is false for a saved key, which would
|
|
257
273
|
* make the reset button skip it and silently leave it stored. The describe
|
|
258
274
|
* sidecar is the one read that carries the fact: a { path, set } entry per
|
|
259
|
-
* schema-declared secret, with the value itself never sent.
|
|
260
|
-
*
|
|
261
|
-
* describe mirror
|
|
275
|
+
* schema-declared secret, with the value itself never sent.
|
|
276
|
+
*
|
|
277
|
+
* The shared describe mirror is reached through `configForms.describe()`.
|
|
278
|
+
* It exposes the whole document — `view.namespaces`, one row per served
|
|
279
|
+
* entry — and the form controller bound to this entry does NOT project the
|
|
280
|
+
* sidecar, so the lookup is done here against the mirror's own snapshot.
|
|
262
281
|
*
|
|
263
282
|
* @returns a map from field key to whether that secret currently has a value.
|
|
264
283
|
*/
|
|
265
284
|
function useSecretStatus(ctx) {
|
|
266
|
-
var mirror = ctx.
|
|
285
|
+
var mirror = ctx.configForms.describe();
|
|
267
286
|
var subscribe = React.useCallback(
|
|
268
287
|
function (onChange) {
|
|
269
288
|
return mirror.subscribe(onChange);
|
|
@@ -276,9 +295,13 @@ window.__ModuleLoader__.load({
|
|
|
276
295
|
},
|
|
277
296
|
[mirror],
|
|
278
297
|
);
|
|
279
|
-
React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
298
|
+
var snapshot = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
280
299
|
|
|
281
|
-
var
|
|
300
|
+
var namespaces = (snapshot && snapshot.view && snapshot.view.namespaces) || [];
|
|
301
|
+
var row = null;
|
|
302
|
+
for (var i = 0; i < namespaces.length; i++) {
|
|
303
|
+
if (namespaces[i].ns === NS) row = namespaces[i];
|
|
304
|
+
}
|
|
282
305
|
var set = {};
|
|
283
306
|
((row && row.secrets) || []).forEach(function (secret) {
|
|
284
307
|
if (secret.path.length === 1 && secret.set) set[secret.path[0]] = true;
|
|
@@ -312,8 +335,9 @@ window.__ModuleLoader__.load({
|
|
|
312
335
|
/**
|
|
313
336
|
* The row's configuration card.
|
|
314
337
|
*
|
|
315
|
-
* `scope` is the
|
|
316
|
-
* `
|
|
338
|
+
* `scope` is the configuration form `apply` obtained for this entry from
|
|
339
|
+
* `configForms.get(NS)`; `ctx` is the scoped context it was obtained on,
|
|
340
|
+
* which supplies the live model directory.
|
|
317
341
|
*/
|
|
318
342
|
function BrowserAgentSettingsCard(props) {
|
|
319
343
|
var scope = props.scope;
|
|
@@ -793,30 +817,44 @@ window.__ModuleLoader__.load({
|
|
|
793
817
|
}
|
|
794
818
|
|
|
795
819
|
/**
|
|
796
|
-
* Client plugin body: bind the
|
|
820
|
+
* Client plugin body: bind the configuration form and register the card.
|
|
821
|
+
*
|
|
822
|
+
* `configForms` is injected NESTED rather than declared in this plugin's
|
|
823
|
+
* `inject` list. A plugin that waits on a service the profile does not
|
|
824
|
+
* provide never activates, and the Web UI's boot audit turns that into a
|
|
825
|
+
* hard failure for the WHOLE page ("web boot: N entries did not activate"),
|
|
826
|
+
* not just for this card. Nested, a host without the settings UI loses only
|
|
827
|
+
* this card — which is the same trade `dsh-market` makes.
|
|
828
|
+
*
|
|
829
|
+
* The nested context inherits the services declared here (`slots`,
|
|
830
|
+
* `remote`), so the card is handed the scoped context and can reach both the
|
|
831
|
+
* form and the model directory through it.
|
|
832
|
+
*
|
|
797
833
|
* @param ctx - client root context.
|
|
798
834
|
*/
|
|
799
835
|
function apply(ctx) {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
836
|
+
ctx.inject(["configForms"], function (scoped) {
|
|
837
|
+
var scope = scoped.configForms.get(NS);
|
|
838
|
+
BUNDLE_NAMES.forEach(function (bundle) {
|
|
839
|
+
scoped.slots.inject("plugins.row.config", function () {
|
|
840
|
+
return scoped.slots.register(
|
|
841
|
+
{
|
|
842
|
+
name: "plugins.row.config",
|
|
843
|
+
key: bundle + "#" + ROW_ID,
|
|
844
|
+
inject: function () {
|
|
845
|
+
return { scope: scope, ctx: scoped };
|
|
846
|
+
},
|
|
809
847
|
},
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
);
|
|
848
|
+
BrowserAgentSettingsCard,
|
|
849
|
+
);
|
|
850
|
+
});
|
|
813
851
|
});
|
|
814
852
|
});
|
|
815
853
|
}
|
|
816
854
|
|
|
817
855
|
return {
|
|
818
856
|
name: "dsh-browser-agent",
|
|
819
|
-
inject: ["slots", "
|
|
857
|
+
inject: ["slots", "remote", "remote.session"],
|
|
820
858
|
apply: apply,
|
|
821
859
|
};
|
|
822
860
|
},
|
package/lib/config.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Configuration surface of the browser agent.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* half reads it when a run starts,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* As of the 0.1.7-alpha.1 settings redesign this schema is the ONE contract
|
|
5
|
+
* every surface shares: the host half reads it when a run starts, and
|
|
6
|
+
* `dsh-settings` derives the Plugins-page form from it — keyed by the loader
|
|
7
|
+
* entry id, which is why {@link ENTRY_ID} must equal both the row id in
|
|
8
|
+
* `cordis.patch.yml` and the host half's `export const name`. The card ships as
|
|
9
|
+
* plain browser code and cannot import this module, so its field list is kept in
|
|
10
|
+
* step by hand — changing a key here means changing `src/client.js` too.
|
|
11
|
+
*
|
|
12
|
+
* Two rules follow from the redesign, and both are load-bearing:
|
|
13
|
+
*
|
|
14
|
+
* - The schema must be reachable as `entry.fiber.runtime.Config`, i.e. exported
|
|
15
|
+
* from the PACKAGE ENTRY (`lib/index.js`), not merely from this module. That
|
|
16
|
+
* is where `dsh-settings` looks; a schema exported only from a subpath is
|
|
17
|
+
* invisible and the entry simply has no form.
|
|
18
|
+
* - Every field must be `volatile()`. The marker is what makes the field
|
|
19
|
+
* editable without a remount — and it is also what makes the Loader hand
|
|
20
|
+
* `apply` a live `{ get() }` reference instead of a value, which is the only
|
|
21
|
+
* shape the host half reads.
|
|
9
22
|
*
|
|
10
23
|
* @module @logictan/dsh-browser-agent/config
|
|
11
24
|
*/
|
|
@@ -14,11 +27,18 @@ import { DEFAULT_ENDPOINT as DEFAULT_CDP_ENDPOINT } from './cdp.js';
|
|
|
14
27
|
import { DEFAULT_MAX_STEPS } from './loop.js';
|
|
15
28
|
import { DEFAULT_ENDPOINT as DEFAULT_TYPESAFE_ENDPOINT, DEFAULT_MODEL } from './typesafe.js';
|
|
16
29
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The loader entry id this plugin's configuration form is keyed by.
|
|
32
|
+
*
|
|
33
|
+
* `dsh-settings` keys a form by the profile entry id, which the repo convention
|
|
34
|
+
* pins to the host half's `export const name`; the browser half passes the same
|
|
35
|
+
* string to `configForms.get`. A mismatch does not throw — the card simply binds
|
|
36
|
+
* a form that is never served, and every field renders as unset.
|
|
37
|
+
*/
|
|
38
|
+
export const ENTRY_ID = 'browser-agent';
|
|
19
39
|
|
|
20
40
|
/**
|
|
21
|
-
* Schema of the `browser-agent`
|
|
41
|
+
* Schema of the `browser-agent` configuration.
|
|
22
42
|
*
|
|
23
43
|
* The TypeSafe key is `role('secret')`: it is stored in the local settings
|
|
24
44
|
* document and stripped by every redacting surface, including config sync. That
|
|
@@ -35,16 +55,20 @@ export const SETTINGS_NAMESPACE = 'browser-agent';
|
|
|
35
55
|
* "use the session's own current route", which is the only default that stays
|
|
36
56
|
* correct after config sync moves the file to a machine with different
|
|
37
57
|
* providers; a hard-coded model would point at a model the target may not have.
|
|
58
|
+
*
|
|
59
|
+
* `volatile()` order matters for the secret: `role()` must come first, because
|
|
60
|
+
* `volatile()` wraps the schema and a second wrap throws `volatile schema is
|
|
61
|
+
* already wrapped`.
|
|
38
62
|
*/
|
|
39
63
|
export const Config = z.object({
|
|
40
|
-
typesafeApiKey: z.string().role('secret'),
|
|
41
|
-
typesafeEndpoint: z.string().default(DEFAULT_TYPESAFE_ENDPOINT),
|
|
42
|
-
typesafeModel: z.string().default(DEFAULT_MODEL),
|
|
43
|
-
cdpEndpoint: z.string().default(DEFAULT_CDP_ENDPOINT),
|
|
44
|
-
maxSteps: z.number().step(1).min(1).default(DEFAULT_MAX_STEPS),
|
|
45
|
-
textProvider: z.string().default(''),
|
|
46
|
-
textModel: z.string().default(''),
|
|
47
|
-
textReasoningEffort: z.string().default(''),
|
|
64
|
+
typesafeApiKey: z.string().role('secret').volatile(),
|
|
65
|
+
typesafeEndpoint: z.string().default(DEFAULT_TYPESAFE_ENDPOINT).volatile(),
|
|
66
|
+
typesafeModel: z.string().default(DEFAULT_MODEL).volatile(),
|
|
67
|
+
cdpEndpoint: z.string().default(DEFAULT_CDP_ENDPOINT).volatile(),
|
|
68
|
+
maxSteps: z.number().step(1).min(1).default(DEFAULT_MAX_STEPS).volatile(),
|
|
69
|
+
textProvider: z.string().default('').volatile(),
|
|
70
|
+
textModel: z.string().default('').volatile(),
|
|
71
|
+
textReasoningEffort: z.string().default('').volatile(),
|
|
48
72
|
});
|
|
49
73
|
|
|
50
74
|
/**
|
|
@@ -60,7 +84,8 @@ export const Config = z.object({
|
|
|
60
84
|
* agent fields off the context short-circuits every optional chain and silently
|
|
61
85
|
* reports "unconfigured" on a session that has a perfectly good route.
|
|
62
86
|
*
|
|
63
|
-
* @param config - the
|
|
87
|
+
* @param config - the configuration in effect, as plain values (the host half
|
|
88
|
+
* unwraps the live references before calling).
|
|
64
89
|
* @param exec - the tool execution context; its `agent` is the caller.
|
|
65
90
|
* @returns the route to call; empty provider/model means "unconfigured".
|
|
66
91
|
*/
|
package/lib/index.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-browser-agent — host half.
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Registers one thing: the `browser_agent` model tool that runs the decision
|
|
5
|
+
* loop over CDP against the user's own Chrome.
|
|
6
|
+
*
|
|
7
|
+
* Configuration arrives as `apply`'s second argument. As of the 0.1.7-alpha.1
|
|
8
|
+
* settings redesign the plugin's own `Config` schema is the only config surface
|
|
9
|
+
* — `dsh-settings` derives the Plugins-page form from `entry.fiber.runtime.Config`,
|
|
10
|
+
* keyed by the entry id — and every field of it is `volatile()`, so the Loader
|
|
11
|
+
* hands `apply` one live `{ get() }` reference per field rather than a value.
|
|
12
|
+
* The former `settings.register(ns, schema)` and `settings.get(ns)` seams were
|
|
13
|
+
* removed in that generation, which is why this plugin failed to activate on it.
|
|
14
|
+
*
|
|
15
|
+
* `Config` is therefore re-exported from the package entry: that is the exact
|
|
16
|
+
* object `dsh-settings` looks for, and a schema exported only from `./config.js`
|
|
17
|
+
* would leave the entry with no form at all.
|
|
7
18
|
*
|
|
8
19
|
* The tool is registered through `ctx.get('tools')` rather than the `ctx.tools`
|
|
9
20
|
* property. Cordis requires a declared `inject` before a service property may
|
|
10
21
|
* be read, and `tools` is genuinely optional here — a headless deployment that
|
|
11
|
-
* composes no tool registry still has a working
|
|
12
|
-
*
|
|
22
|
+
* composes no tool registry still has a working configuration — so it must not
|
|
23
|
+
* be injected. Reading it optionally is the same choice
|
|
13
24
|
* `dsh-config-manager`'s `registerModelTools` makes, for the same reason.
|
|
14
25
|
*
|
|
15
26
|
* @module @logictan/dsh-browser-agent
|
|
@@ -17,9 +28,11 @@
|
|
|
17
28
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
18
29
|
|
|
19
30
|
import { attach, activePage } from './cdp.js';
|
|
20
|
-
import {
|
|
31
|
+
import { resolveTextRoute } from './config.js';
|
|
21
32
|
import { run } from './loop.js';
|
|
22
33
|
|
|
34
|
+
export { Config } from './config.js';
|
|
35
|
+
|
|
23
36
|
/** Plugin row id; must equal the row id in `cordis.patch.yml`. */
|
|
24
37
|
export const name = 'browser-agent';
|
|
25
38
|
|
|
@@ -27,25 +40,47 @@ export const name = 'browser-agent';
|
|
|
27
40
|
* Services this plugin needs before it can mount.
|
|
28
41
|
*
|
|
29
42
|
* `llm` is required: the TYPE_TEXT step cannot work without it, and a silent
|
|
30
|
-
* degradation there would be worse than a load failure. `settings` is
|
|
31
|
-
*
|
|
43
|
+
* degradation there would be worse than a load failure. `settings` is NOT
|
|
44
|
+
* injected — the plugin neither reads nor registers a settings namespace any
|
|
45
|
+
* more, and waiting on a service a profile does not provide is precisely what
|
|
46
|
+
* left this entry `pending` (and took the whole Web UI's boot audit down) on
|
|
47
|
+
* 0.1.7-alpha.1.
|
|
32
48
|
*/
|
|
33
|
-
export const inject = ['
|
|
49
|
+
export const inject = ['llm'];
|
|
34
50
|
|
|
35
51
|
/**
|
|
36
52
|
* Register the `browser_agent` tool.
|
|
37
53
|
*
|
|
38
54
|
* @param ctx - the plugin context.
|
|
55
|
+
* @param config - one live reference per {@link Config} field.
|
|
39
56
|
*/
|
|
40
|
-
export function apply(ctx) {
|
|
41
|
-
const settings = ctx.settings;
|
|
57
|
+
export function apply(ctx, config) {
|
|
42
58
|
const llm = ctx.llm;
|
|
43
59
|
const tools = ctx.get('tools');
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
/**
|
|
62
|
+
* The configuration in effect right now, as plain values.
|
|
63
|
+
*
|
|
64
|
+
* Resolved at each use rather than captured at activation: the Loader commits
|
|
65
|
+
* a Plugins-page edit into these very references (`loader/volatile-update`)
|
|
66
|
+
* instead of remounting the plugin, so a snapshot taken here would pin the
|
|
67
|
+
* values the profile started with and the card's save button would appear to
|
|
68
|
+
* do nothing.
|
|
69
|
+
*
|
|
70
|
+
* @returns the resolved configuration.
|
|
71
|
+
*/
|
|
72
|
+
function currentConfig() {
|
|
73
|
+
return {
|
|
74
|
+
typesafeApiKey: config.typesafeApiKey.get(),
|
|
75
|
+
typesafeEndpoint: config.typesafeEndpoint.get(),
|
|
76
|
+
typesafeModel: config.typesafeModel.get(),
|
|
77
|
+
cdpEndpoint: config.cdpEndpoint.get(),
|
|
78
|
+
maxSteps: config.maxSteps.get(),
|
|
79
|
+
textProvider: config.textProvider.get(),
|
|
80
|
+
textModel: config.textModel.get(),
|
|
81
|
+
textReasoningEffort: config.textReasoningEffort.get(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
49
84
|
|
|
50
85
|
const definition = defineTool({
|
|
51
86
|
name: 'browser_agent',
|
|
@@ -62,7 +97,7 @@ export function apply(ctx) {
|
|
|
62
97
|
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
63
98
|
},
|
|
64
99
|
async execute(args, exec) {
|
|
65
|
-
const current =
|
|
100
|
+
const current = currentConfig();
|
|
66
101
|
// An unset optional field resolves to undefined; a cleared one to ''.
|
|
67
102
|
if (current.typesafeApiKey === undefined || current.typesafeApiKey === '') {
|
|
68
103
|
throw new Error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logictan/dsh-browser-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DSH browser agent: a dynamic operation+target decision loop. TypeSafe picks the action, this plugin executes it over CDP against the user's own Chrome. DSH 浏览器 Agent 子插件。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"client": {
|
|
17
17
|
"inject": [
|
|
18
18
|
"slots",
|
|
19
|
-
"settingsScope",
|
|
20
19
|
"remote",
|
|
21
20
|
"remote.session"
|
|
22
21
|
],
|
|
@@ -59,9 +58,8 @@
|
|
|
59
58
|
"peerDependencies": {
|
|
60
59
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
61
60
|
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
|
|
62
|
-
"@deepseek-ai/dsh-settings": "^0.1.6-alpha.2",
|
|
63
61
|
"@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
|
|
64
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
62
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
65
63
|
},
|
|
66
64
|
"peerDependenciesMeta": {
|
|
67
65
|
"@deepseek-ai/cordis": {
|
|
@@ -70,9 +68,6 @@
|
|
|
70
68
|
"@deepseek-ai/dsh-llm": {
|
|
71
69
|
"optional": true
|
|
72
70
|
},
|
|
73
|
-
"@deepseek-ai/dsh-settings": {
|
|
74
|
-
"optional": true
|
|
75
|
-
},
|
|
76
71
|
"@deepseek-ai/dsh-tools": {
|
|
77
72
|
"optional": true
|
|
78
73
|
},
|
|
@@ -83,8 +78,7 @@
|
|
|
83
78
|
"devDependencies": {
|
|
84
79
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
85
80
|
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
|
|
86
|
-
"@deepseek-ai/dsh-settings": "^0.1.6-alpha.2",
|
|
87
81
|
"@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
|
|
88
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
82
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
89
83
|
}
|
|
90
84
|
}
|