@jobshimo/browser-link 0.42.0 → 0.43.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/README.md +16 -0
- package/dist/activity/db.js +18 -0
- package/dist/activity/db.js.map +1 -1
- package/dist/activity/extract.d.ts +18 -2
- package/dist/activity/extract.js +122 -1
- package/dist/activity/extract.js.map +1 -1
- package/dist/activity/queries.d.ts +1 -1
- package/dist/activity/queries.js +20 -4
- package/dist/activity/queries.js.map +1 -1
- package/dist/activity/summary.d.ts +54 -0
- package/dist/activity/summary.js +95 -0
- package/dist/activity/summary.js.map +1 -0
- package/dist/activity/types.d.ts +30 -0
- package/dist/activity/types.js +31 -1
- package/dist/activity/types.js.map +1 -1
- package/dist/cli.js +18 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/activity.d.ts +5 -0
- package/dist/commands/activity.js +92 -1
- package/dist/commands/activity.js.map +1 -1
- package/dist/commands/config.d.ts +14 -0
- package/dist/commands/config.js +48 -4
- package/dist/commands/config.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js.map +1 -1
- package/dist/extension/manifest.json +1 -1
- package/dist/manual/topics.js +2 -2
- package/dist/manual/topics.js.map +1 -1
- package/dist/map/db.js +26 -0
- package/dist/map/db.js.map +1 -1
- package/dist/map/learning.d.ts +81 -0
- package/dist/map/learning.js +244 -0
- package/dist/map/learning.js.map +1 -0
- package/dist/map/queries.d.ts +45 -0
- package/dist/map/queries.js +108 -5
- package/dist/map/queries.js.map +1 -1
- package/dist/map/tools.js +2 -1
- package/dist/map/tools.js.map +1 -1
- package/dist/server.js +9 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +15 -1
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +10 -0
- package/dist/tools/browser-dispatch.js +88 -18
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/package.json +1 -1
package/dist/manual/topics.js
CHANGED
|
@@ -37,7 +37,7 @@ export const TOPICS = {
|
|
|
37
37
|
},
|
|
38
38
|
map: {
|
|
39
39
|
title: 'The persistent map — identifying the app, mechanisms and pitfalls',
|
|
40
|
-
body: lines('The map is a private
|
|
40
|
+
body: lines('The map is a private per-machine SQLite cache of UI structure in the data dir ($BROWSER_LINK_DATA_DIR overrides it), never in a repo.', '- `origin` = scheme://host:port of the tab. `app_key` tells apart apps that share an origin; omit it on the first save (from the page title) and fix a poor guess with `browser.map.rename_app`.', '- After a non-trivial flow worked end-to-end, `browser.map.save` the selectors you just executed, optionally with `flows` recipes that use placeholders (`<QUERY>`, `<NAME>`) for every free-text value.', '- Save how the app behaves underneath too: `mechanisms: [{ key, description }]` ("a GET of an item URL marks it complete server-side") and `pitfalls: [{ key, description, symptom? }]` ("click on the next link returns ok but does not navigate"). A pitfall costs far more to rediscover than a selector; `recall` returns pitfalls first.', '- A `find` single match carries `semantic_key`. When the selector you save is structural (`:nth-of-type`), save that `semantic_key` in the payload next to it, and use `browser.find({ key })` when recall returns `prefer: "semantic_key"`. Never save a key for an `ambiguous` entry.', '- Never save a `ref` or an `ambiguous` selector, and never domain data: no IDs, names, dates or real values.', '- It learns alone: `recall`/`list_tabs` show `learned` counts per selector or `find` key, failed first: avoid those. Never values. Off: `browser-link config set map-learning off`.'),
|
|
41
41
|
},
|
|
42
42
|
'token-efficiency': {
|
|
43
43
|
title: 'Token-efficient patterns and reading data',
|
|
@@ -45,7 +45,7 @@ export const TOPICS = {
|
|
|
45
45
|
},
|
|
46
46
|
'multi-agent': {
|
|
47
47
|
title: 'Sharing tabs with other agents (multi-agent mode)',
|
|
48
|
-
body: lines('Several MCP clients can share one bridge; a cooperative claim layer keeps two agents off the same tab.', '- Action tools (`browser.click`, `browser.type`, `browser.navigate`, `browser.evaluate`) auto-claim a free tab on first use. If another agent holds it they fail naming the owner: ask the user whose tab it is, or use a different one.', '- Read tools (`browser.snapshot`, `browser.state`, `browser.console`, `browser.network`, `browser.network_body`, `browser.events`, `browser.ping`) ignore claims.', '- Give `browser.claim_tab` a stable `label` ("claude-code", "opencode"); claims also end on disconnect and after the inactivity TTL. The label is display only: ownership rests on the IPC session id, not on what an agent calls itself.', '- One server process is the primary. A newer browser-link that starts displaces an older primary (it reads `GET /version` on the bridge port, then stops the old process), and the extension reconnects by itself.', '- A `list_tabs` hint "server running X, installed Y" means the package was updated under a live server: ask the user to run `browser-link stop` and restart the agent, or to click "Repair connection" in the extension popup. Do not kill processes yourself.'),
|
|
48
|
+
body: lines('Several MCP clients can share one bridge; a cooperative claim layer keeps two agents off the same tab.', '- Action tools (`browser.click`, `browser.type`, `browser.navigate`, `browser.evaluate`) auto-claim a free tab on first use. If another agent holds it they fail naming the owner: ask the user whose tab it is, or use a different one.', '- Read tools (`browser.snapshot`, `browser.state`, `browser.console`, `browser.network`, `browser.network_body`, `browser.events`, `browser.ping`) ignore claims.', '- Give `browser.claim_tab` a stable `label` ("claude-code", "opencode"); claims also end on disconnect and after the inactivity TTL. The label is display only: ownership rests on the IPC session id, not on what an agent calls itself.', '- One server process is the primary. A newer browser-link that starts displaces an older primary (it reads `GET /version` on the bridge port, then stops the old process), and the extension reconnects by itself.', '- `browser.activity({ summary: true })` is the friction report: errors and flagged calls (`not-found`, `unsettled`, flow or download `timeout`...) by flag, by tool, top failing selectors and one line per agent session; narrow it with `since`, `since_id` or `agent_id`.', '- A `list_tabs` hint "server running X, installed Y" means the package was updated under a live server: ask the user to run `browser-link stop` and restart the agent, or to click "Repair connection" in the extension popup. Do not kill processes yourself.'),
|
|
49
49
|
},
|
|
50
50
|
'cdp-direct': {
|
|
51
51
|
title: 'cdp-direct tabs (optional, off by default, human-gated)',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topics.js","sourceRoot":"","sources":["../../src/manual/topics.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;IACX,QAAQ;IACR,YAAY;IACZ,OAAO;IACP,KAAK;IACL,kBAAkB;IAClB,aAAa;IACb,YAAY;CACJ,CAAC;AASX,MAAM,KAAK,GAAG,CAAC,GAAG,CAAW,EAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEvD,MAAM,CAAC,MAAM,MAAM,GAA6B;IAC9C,SAAS,EAAE;QACT,KAAK,EAAE,2CAA2C;QAClD,IAAI,EAAE,KAAK,CACT,kOAAkO,EAClO,+OAA+O,EAC/O,0EAA0E,EAC1E,uHAAuH,CACxH;KACF;IACD,MAAM,EAAE;QACN,KAAK,EAAE,0DAA0D;QACjE,IAAI,EAAE,KAAK,CACT,+OAA+O,EAC/O,oPAAoP,EACpP,oSAAoS,CACrS;KACF;IACD,UAAU,EAAE;QACV,KAAK,EAAE,mCAAmC;QAC1C,IAAI,EAAE,KAAK,CACT,wKAAwK,EACxK,oLAAoL,EACpL,oFAAoF,CACrF;KACF;IACD,KAAK,EAAE;QACL,KAAK,EAAE,wDAAwD;QAC/D,IAAI,EAAE,KAAK,CACT,gaAAga,EACha,EAAE,EACF,iJAAiJ,EACjJ,6HAA6H,EAC7H,iOAAiO,EACjO,gWAAgW,EAChW,qIAAqI,EACrI,sXAAsX,EACtX,kMAAkM,EAClM,EAAE,EACF,6aAA6a,EAC7a,EAAE,EACF,2XAA2X,EAC3X,kNAAkN,EAClN,qGAAqG,EACrG,uGAAuG,EACvG,mLAAmL,CACpL;KACF;IACD,GAAG,EAAE;QACH,KAAK,EAAE,mEAAmE;QAC1E,IAAI,EAAE,KAAK,CACT,
|
|
1
|
+
{"version":3,"file":"topics.js","sourceRoot":"","sources":["../../src/manual/topics.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;IACX,QAAQ;IACR,YAAY;IACZ,OAAO;IACP,KAAK;IACL,kBAAkB;IAClB,aAAa;IACb,YAAY;CACJ,CAAC;AASX,MAAM,KAAK,GAAG,CAAC,GAAG,CAAW,EAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEvD,MAAM,CAAC,MAAM,MAAM,GAA6B;IAC9C,SAAS,EAAE;QACT,KAAK,EAAE,2CAA2C;QAClD,IAAI,EAAE,KAAK,CACT,kOAAkO,EAClO,+OAA+O,EAC/O,0EAA0E,EAC1E,uHAAuH,CACxH;KACF;IACD,MAAM,EAAE;QACN,KAAK,EAAE,0DAA0D;QACjE,IAAI,EAAE,KAAK,CACT,+OAA+O,EAC/O,oPAAoP,EACpP,oSAAoS,CACrS;KACF;IACD,UAAU,EAAE;QACV,KAAK,EAAE,mCAAmC;QAC1C,IAAI,EAAE,KAAK,CACT,wKAAwK,EACxK,oLAAoL,EACpL,oFAAoF,CACrF;KACF;IACD,KAAK,EAAE;QACL,KAAK,EAAE,wDAAwD;QAC/D,IAAI,EAAE,KAAK,CACT,gaAAga,EACha,EAAE,EACF,iJAAiJ,EACjJ,6HAA6H,EAC7H,iOAAiO,EACjO,gWAAgW,EAChW,qIAAqI,EACrI,sXAAsX,EACtX,kMAAkM,EAClM,EAAE,EACF,6aAA6a,EAC7a,EAAE,EACF,2XAA2X,EAC3X,kNAAkN,EAClN,qGAAqG,EACrG,uGAAuG,EACvG,mLAAmL,CACpL;KACF;IACD,GAAG,EAAE;QACH,KAAK,EAAE,mEAAmE;QAC1E,IAAI,EAAE,KAAK,CACT,uIAAuI,EACvI,kMAAkM,EAClM,0MAA0M,EAC1M,+UAA+U,EAC/U,yRAAyR,EACzR,8GAA8G,EAC9G,qLAAqL,CACtL;KACF;IACD,kBAAkB,EAAE;QAClB,KAAK,EAAE,2CAA2C;QAClD,IAAI,EAAE,KAAK,CACT,kGAAkG,EAClG,qRAAqR,EACrR,iJAAiJ,EACjJ,gNAAgN,EAChN,wFAAwF,EACxF,2HAA2H,EAC3H,iGAAiG,EACjG,sPAAsP,EACtP,uaAAua,EACva,sQAAsQ,EACtQ,+HAA+H,EAC/H,2QAA2Q,EAC3Q,oNAAoN,CACrN;KACF;IACD,aAAa,EAAE;QACb,KAAK,EAAE,mDAAmD;QAC1D,IAAI,EAAE,KAAK,CACT,wGAAwG,EACxG,0OAA0O,EAC1O,mKAAmK,EACnK,2OAA2O,EAC3O,oNAAoN,EACpN,8QAA8Q,EAC9Q,gQAAgQ,CACjQ;KACF;IACD,YAAY,EAAE;QACZ,KAAK,EAAE,yDAAyD;QAChE,IAAI,EAAE,KAAK,CACT,yOAAyO,EACzO,gIAAgI,EAChI,qHAAqH,EACrH,0GAA0G,CAC3G;KACF;CACF,CAAC;AAEF,wEAAwE;AACxE,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,OAAO,KAAK,CAAC,KAAK,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AAC/C,CAAC"}
|
package/dist/map/db.js
CHANGED
|
@@ -121,6 +121,32 @@ function runMigrations(db) {
|
|
|
121
121
|
);
|
|
122
122
|
|
|
123
123
|
CREATE INDEX IF NOT EXISTS idx_insights_app ON insights(app_id);
|
|
124
|
+
`);
|
|
125
|
+
// v0.43.0: what the map learned from use, with no bookkeeping by the agent
|
|
126
|
+
// (see map/learning.ts). One row per durable target (a CSS selector or a
|
|
127
|
+
// `browser.find` semantic key, never typed text or values) per action per
|
|
128
|
+
// route, with ok/fail counters. Additive CREATE-IF-NOT-EXISTS like every
|
|
129
|
+
// table above: an older DB picks it up on next open, no row is rewritten.
|
|
130
|
+
// `last_reason` holds a fixed enum (`LEARNED_REASONS` in learning.ts),
|
|
131
|
+
// enforced in code rather than by a CHECK, so widening the enum later
|
|
132
|
+
// never means rebuilding this table (see the `insights` comment).
|
|
133
|
+
db.exec(`
|
|
134
|
+
CREATE TABLE IF NOT EXISTS selector_outcomes (
|
|
135
|
+
id INTEGER PRIMARY KEY,
|
|
136
|
+
app_id INTEGER NOT NULL REFERENCES apps(id) ON DELETE CASCADE,
|
|
137
|
+
url_pattern TEXT NOT NULL,
|
|
138
|
+
target TEXT NOT NULL,
|
|
139
|
+
target_kind TEXT NOT NULL CHECK (target_kind IN ('selector', 'key')),
|
|
140
|
+
action TEXT NOT NULL,
|
|
141
|
+
ok_n INTEGER NOT NULL DEFAULT 0,
|
|
142
|
+
fail_n INTEGER NOT NULL DEFAULT 0,
|
|
143
|
+
last_reason TEXT,
|
|
144
|
+
last_ok_at TEXT,
|
|
145
|
+
last_fail_at TEXT,
|
|
146
|
+
UNIQUE(app_id, url_pattern, target, target_kind, action)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_selector_outcomes_app ON selector_outcomes(app_id);
|
|
124
150
|
`);
|
|
125
151
|
// v0.20.0: normalize apps.origin rows written before the write path
|
|
126
152
|
// canonicalized origins (see origin.ts). Before this release, save
|
package/dist/map/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/map/db.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/map/db.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
/**
|
|
3
|
+
* The map learns from use.
|
|
4
|
+
*
|
|
5
|
+
* Every click/type/press/find/wait_for an agent dispatches already tells us
|
|
6
|
+
* whether a target worked: a thrown `retargeted`, a `find` that resolved with
|
|
7
|
+
* `matched: false`, a click whose `expect` was met. Until now that evidence
|
|
8
|
+
* died with the tool result. This module turns it into ok/fail counters per
|
|
9
|
+
* durable target in the map DB (`selector_outcomes`, see db.ts), so the
|
|
10
|
+
* `list_tabs` hint and `browser.map.recall` can say "this selector failed
|
|
11
|
+
* here before" without the agent doing any bookkeeping.
|
|
12
|
+
*
|
|
13
|
+
* Privacy rule: the stored target is the CSS selector string or the
|
|
14
|
+
* `browser.find` semantic key, and nothing else. Typed text, evaluate
|
|
15
|
+
* expressions, key names and any other value an action carried are never
|
|
16
|
+
* read here.
|
|
17
|
+
*
|
|
18
|
+
* Two layers:
|
|
19
|
+
* - `classifyOutcome` is pure: (tool, args, result, error) → verdict.
|
|
20
|
+
* - `recordOutcome` persists one verdict; `learnFromDispatch` is the glue
|
|
21
|
+
* the dispatcher's `recordOutcome` dep calls, gated by the config switch.
|
|
22
|
+
*/
|
|
23
|
+
/** The fixed failure vocabulary. `last_reason` never holds anything else. */
|
|
24
|
+
export declare const LEARNED_REASONS: readonly ["retargeted", "ref-stale", "expect-failed", "not-found", "multiple-matches", "unmatched", "timeout", "unsettled", "hit-mismatch"];
|
|
25
|
+
export type LearnedReason = (typeof LEARNED_REASONS)[number];
|
|
26
|
+
export type LearnedOutcome = {
|
|
27
|
+
ok: true;
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
reason: LearnedReason;
|
|
31
|
+
};
|
|
32
|
+
export type TargetKind = 'selector' | 'key';
|
|
33
|
+
/** Rows kept per app. Past it, the least used row is evicted. */
|
|
34
|
+
export declare const MAX_LEARNED_PER_APP = 200;
|
|
35
|
+
/**
|
|
36
|
+
* Classify one dispatched call.
|
|
37
|
+
*
|
|
38
|
+
* Returns `{ ok: true }`, `{ ok: false, reason }`, or null when there is
|
|
39
|
+
* nothing to learn: a tool outside click/type/press/find/wait_for, a call
|
|
40
|
+
* without a durable target, or a result that proves neither success nor
|
|
41
|
+
* failure (a quiet click with no `expect`: quiet is not effect).
|
|
42
|
+
*/
|
|
43
|
+
export declare function classifyOutcome(tool: string, args: unknown, result: unknown, error: unknown): LearnedOutcome | null;
|
|
44
|
+
export interface RecordOutcomeInput {
|
|
45
|
+
origin: string;
|
|
46
|
+
pathname: string;
|
|
47
|
+
target: string;
|
|
48
|
+
target_kind: TargetKind;
|
|
49
|
+
action: string;
|
|
50
|
+
outcome: LearnedOutcome;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Persist one verdict: upsert the counters of the (app, route, target,
|
|
54
|
+
* kind, action) row, then enforce the per-app cap.
|
|
55
|
+
*
|
|
56
|
+
* The app is resolved by `resolveLearnerApp`: the most recently seen app
|
|
57
|
+
* of the origin (the one `list_tabs` and `recall` show), left untouched,
|
|
58
|
+
* or a new app keyed by the origin host when the origin has none. The tab
|
|
59
|
+
* title is never an input: a title such as "Inbox (3) - My App" would mint
|
|
60
|
+
* a fresh app that then hides the saved map.
|
|
61
|
+
*/
|
|
62
|
+
export declare function recordOutcome(db: Database.Database, input: RecordOutcomeInput): void;
|
|
63
|
+
/** What the dispatcher hands the learner after a call settles. */
|
|
64
|
+
export interface DispatchOutcomeContext {
|
|
65
|
+
tool: string;
|
|
66
|
+
args: unknown;
|
|
67
|
+
result: unknown;
|
|
68
|
+
error: unknown;
|
|
69
|
+
/** The tab's URL after the call, from the live tab table. */
|
|
70
|
+
url: string | null;
|
|
71
|
+
/** The tab's title. Part of the dispatcher contract but deliberately
|
|
72
|
+
* ignored by the learner: it never resolves apps from or stores titles. */
|
|
73
|
+
title: string | null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The dispatcher's `recordOutcome` dep in production (wired in server.ts).
|
|
77
|
+
* Classifies first, so calls with nothing to learn cost no config read and
|
|
78
|
+
* no DB access; then honours the `mapLearningEnabled` switch, read on every
|
|
79
|
+
* call so `browser-link config set map-learning off` applies at once.
|
|
80
|
+
*/
|
|
81
|
+
export declare function learnFromDispatch(ctx: DispatchOutcomeContext): void;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { loadConfig } from '../config.js';
|
|
2
|
+
import { getDb } from './db.js';
|
|
3
|
+
import { resolveLearnerApp } from './queries.js';
|
|
4
|
+
/**
|
|
5
|
+
* The map learns from use.
|
|
6
|
+
*
|
|
7
|
+
* Every click/type/press/find/wait_for an agent dispatches already tells us
|
|
8
|
+
* whether a target worked: a thrown `retargeted`, a `find` that resolved with
|
|
9
|
+
* `matched: false`, a click whose `expect` was met. Until now that evidence
|
|
10
|
+
* died with the tool result. This module turns it into ok/fail counters per
|
|
11
|
+
* durable target in the map DB (`selector_outcomes`, see db.ts), so the
|
|
12
|
+
* `list_tabs` hint and `browser.map.recall` can say "this selector failed
|
|
13
|
+
* here before" without the agent doing any bookkeeping.
|
|
14
|
+
*
|
|
15
|
+
* Privacy rule: the stored target is the CSS selector string or the
|
|
16
|
+
* `browser.find` semantic key, and nothing else. Typed text, evaluate
|
|
17
|
+
* expressions, key names and any other value an action carried are never
|
|
18
|
+
* read here.
|
|
19
|
+
*
|
|
20
|
+
* Two layers:
|
|
21
|
+
* - `classifyOutcome` is pure: (tool, args, result, error) → verdict.
|
|
22
|
+
* - `recordOutcome` persists one verdict; `learnFromDispatch` is the glue
|
|
23
|
+
* the dispatcher's `recordOutcome` dep calls, gated by the config switch.
|
|
24
|
+
*/
|
|
25
|
+
/** The fixed failure vocabulary. `last_reason` never holds anything else. */
|
|
26
|
+
export const LEARNED_REASONS = [
|
|
27
|
+
'retargeted',
|
|
28
|
+
'ref-stale',
|
|
29
|
+
'expect-failed',
|
|
30
|
+
'not-found',
|
|
31
|
+
'multiple-matches',
|
|
32
|
+
'unmatched',
|
|
33
|
+
'timeout',
|
|
34
|
+
'unsettled',
|
|
35
|
+
'hit-mismatch',
|
|
36
|
+
];
|
|
37
|
+
/** Rows kept per app. Past it, the least used row is evicted. */
|
|
38
|
+
export const MAX_LEARNED_PER_APP = 200;
|
|
39
|
+
/** Tools whose outcome says something about a target, and the short action
|
|
40
|
+
* name stored for them. */
|
|
41
|
+
const LEARNABLE_ACTIONS = {
|
|
42
|
+
'browser.click': 'click',
|
|
43
|
+
'browser.type': 'type',
|
|
44
|
+
'browser.press': 'press',
|
|
45
|
+
'browser.find': 'find',
|
|
46
|
+
'browser.wait_for': 'wait_for',
|
|
47
|
+
};
|
|
48
|
+
function asRecord(value) {
|
|
49
|
+
return typeof value === 'object' && value !== null ? value : null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The durable target of a call, or null. A selector string counts for every
|
|
53
|
+
* learnable tool. A semantic key counts only for `browser.find` — on
|
|
54
|
+
* `browser.press`, `key` is the keyboard key, not an element. A `ref` (or
|
|
55
|
+
* `find` by text) names no durable target: refs die with the document and
|
|
56
|
+
* text is a value, so such calls learn nothing.
|
|
57
|
+
*/
|
|
58
|
+
function durableTarget(tool, args) {
|
|
59
|
+
const a = asRecord(args);
|
|
60
|
+
if (!a)
|
|
61
|
+
return null;
|
|
62
|
+
if (typeof a.selector === 'string' && a.selector.length > 0) {
|
|
63
|
+
return { target: a.selector, target_kind: 'selector' };
|
|
64
|
+
}
|
|
65
|
+
if (tool === 'browser.find') {
|
|
66
|
+
const key = asRecord(a.key);
|
|
67
|
+
if (key) {
|
|
68
|
+
// Fixed field order so the same key always yields the same row.
|
|
69
|
+
return {
|
|
70
|
+
target: JSON.stringify({ role: key.role, name: key.name, path: key.path }),
|
|
71
|
+
target_kind: 'key',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
/** Results normally arrive raw; an MCP `{ content: [{ type: 'text', text }] }`
|
|
78
|
+
* envelope is read through its first text block. Unparseable text is no
|
|
79
|
+
* result at all. */
|
|
80
|
+
function unwrapEnvelope(result) {
|
|
81
|
+
const content = asRecord(result)?.content;
|
|
82
|
+
if (!Array.isArray(content))
|
|
83
|
+
return result;
|
|
84
|
+
const first = asRecord(content[0]);
|
|
85
|
+
if (first?.type !== 'text' || typeof first.text !== 'string')
|
|
86
|
+
return null;
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(first.text);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function errorText(error) {
|
|
95
|
+
if (error instanceof Error)
|
|
96
|
+
return error.message;
|
|
97
|
+
return typeof error === 'string' ? error : String(error);
|
|
98
|
+
}
|
|
99
|
+
/** Thrown failures with a known meaning. Anything else (argument
|
|
100
|
+
* validation, claim conflicts, a disconnected tab) says nothing about the
|
|
101
|
+
* target and is not learned. */
|
|
102
|
+
function classifyError(error) {
|
|
103
|
+
const message = errorText(error);
|
|
104
|
+
if (/\bretargeted\b|^Click would land on /.test(message)) {
|
|
105
|
+
return { ok: false, reason: 'retargeted' };
|
|
106
|
+
}
|
|
107
|
+
if (/\bref-stale\b/.test(message))
|
|
108
|
+
return { ok: false, reason: 'ref-stale' };
|
|
109
|
+
if (/expectation not met/.test(message))
|
|
110
|
+
return { ok: false, reason: 'expect-failed' };
|
|
111
|
+
if (/^Tool '.*' timed out after/.test(message))
|
|
112
|
+
return { ok: false, reason: 'timeout' };
|
|
113
|
+
if (/^Element not found:/.test(message))
|
|
114
|
+
return { ok: false, reason: 'not-found' };
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Classify one dispatched call.
|
|
119
|
+
*
|
|
120
|
+
* Returns `{ ok: true }`, `{ ok: false, reason }`, or null when there is
|
|
121
|
+
* nothing to learn: a tool outside click/type/press/find/wait_for, a call
|
|
122
|
+
* without a durable target, or a result that proves neither success nor
|
|
123
|
+
* failure (a quiet click with no `expect`: quiet is not effect).
|
|
124
|
+
*/
|
|
125
|
+
export function classifyOutcome(tool, args, result, error) {
|
|
126
|
+
if (LEARNABLE_ACTIONS[tool] === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
const target = durableTarget(tool, args);
|
|
129
|
+
if (!target)
|
|
130
|
+
return null;
|
|
131
|
+
if (error !== undefined)
|
|
132
|
+
return classifyError(error);
|
|
133
|
+
const r = asRecord(unwrapEnvelope(result)) ?? {};
|
|
134
|
+
if (tool === 'browser.find' || tool === 'browser.wait_for') {
|
|
135
|
+
if (r.matched === true)
|
|
136
|
+
return { ok: true };
|
|
137
|
+
if (r.matched !== false)
|
|
138
|
+
return null;
|
|
139
|
+
if (tool === 'browser.find' && r.reason === 'not-found') {
|
|
140
|
+
return { ok: false, reason: 'not-found' };
|
|
141
|
+
}
|
|
142
|
+
if (tool === 'browser.find' && r.reason === 'multiple-matches') {
|
|
143
|
+
return { ok: false, reason: 'multiple-matches' };
|
|
144
|
+
}
|
|
145
|
+
return { ok: false, reason: 'unmatched' };
|
|
146
|
+
}
|
|
147
|
+
// click / type / press resolved.
|
|
148
|
+
if (typeof r.hit_element === 'string' &&
|
|
149
|
+
target.target_kind === 'selector' &&
|
|
150
|
+
r.hit_element !== target.target) {
|
|
151
|
+
return { ok: false, reason: 'hit-mismatch' };
|
|
152
|
+
}
|
|
153
|
+
// An `expect` that did not hold would have thrown, so a resolved call
|
|
154
|
+
// carrying one is proof of effect.
|
|
155
|
+
if (asRecord(args)?.expect !== undefined)
|
|
156
|
+
return { ok: true };
|
|
157
|
+
const settle = asRecord(r.settle);
|
|
158
|
+
if (settle?.settled === false)
|
|
159
|
+
return { ok: false, reason: 'unsettled' };
|
|
160
|
+
if (typeof settle?.mutation_count === 'number' && settle.mutation_count > 0) {
|
|
161
|
+
return { ok: true };
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Persist one verdict: upsert the counters of the (app, route, target,
|
|
167
|
+
* kind, action) row, then enforce the per-app cap.
|
|
168
|
+
*
|
|
169
|
+
* The app is resolved by `resolveLearnerApp`: the most recently seen app
|
|
170
|
+
* of the origin (the one `list_tabs` and `recall` show), left untouched,
|
|
171
|
+
* or a new app keyed by the origin host when the origin has none. The tab
|
|
172
|
+
* title is never an input: a title such as "Inbox (3) - My App" would mint
|
|
173
|
+
* a fresh app that then hides the saved map.
|
|
174
|
+
*/
|
|
175
|
+
export function recordOutcome(db, input) {
|
|
176
|
+
if (!input.outcome.ok && !LEARNED_REASONS.includes(input.outcome.reason))
|
|
177
|
+
return;
|
|
178
|
+
const app = resolveLearnerApp(db, input.origin);
|
|
179
|
+
const ts = new Date().toISOString();
|
|
180
|
+
const ok = input.outcome.ok;
|
|
181
|
+
const reason = input.outcome.ok ? null : input.outcome.reason;
|
|
182
|
+
db.transaction(() => {
|
|
183
|
+
db.prepare(`INSERT INTO selector_outcomes
|
|
184
|
+
(app_id, url_pattern, target, target_kind, action, ok_n, fail_n, last_reason, last_ok_at, last_fail_at)
|
|
185
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
186
|
+
ON CONFLICT(app_id, url_pattern, target, target_kind, action) DO UPDATE SET
|
|
187
|
+
ok_n = ok_n + excluded.ok_n,
|
|
188
|
+
fail_n = fail_n + excluded.fail_n,
|
|
189
|
+
last_reason = COALESCE(excluded.last_reason, last_reason),
|
|
190
|
+
last_ok_at = COALESCE(excluded.last_ok_at, last_ok_at),
|
|
191
|
+
last_fail_at = COALESCE(excluded.last_fail_at, last_fail_at)`).run(app.id, input.pathname, input.target, input.target_kind, input.action, ok ? 1 : 0, ok ? 0 : 1, reason, ok ? ts : null, ok ? null : ts);
|
|
192
|
+
const { n } = db
|
|
193
|
+
.prepare('SELECT COUNT(*) AS n FROM selector_outcomes WHERE app_id = ?')
|
|
194
|
+
.get(app.id);
|
|
195
|
+
if (n <= MAX_LEARNED_PER_APP)
|
|
196
|
+
return;
|
|
197
|
+
// Evict the least used rows, oldest first on a tie, never the row just
|
|
198
|
+
// written: evicting the newcomer would freeze a full app forever.
|
|
199
|
+
db.prepare(`DELETE FROM selector_outcomes WHERE id IN (
|
|
200
|
+
SELECT id FROM selector_outcomes
|
|
201
|
+
WHERE app_id = ?
|
|
202
|
+
AND NOT (url_pattern = ? AND target = ? AND target_kind = ? AND action = ?)
|
|
203
|
+
ORDER BY ok_n + fail_n ASC,
|
|
204
|
+
MAX(COALESCE(last_ok_at, ''), COALESCE(last_fail_at, '')) ASC,
|
|
205
|
+
id ASC
|
|
206
|
+
LIMIT ?
|
|
207
|
+
)`).run(app.id, input.pathname, input.target, input.target_kind, input.action, n - MAX_LEARNED_PER_APP);
|
|
208
|
+
})();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The dispatcher's `recordOutcome` dep in production (wired in server.ts).
|
|
212
|
+
* Classifies first, so calls with nothing to learn cost no config read and
|
|
213
|
+
* no DB access; then honours the `mapLearningEnabled` switch, read on every
|
|
214
|
+
* call so `browser-link config set map-learning off` applies at once.
|
|
215
|
+
*/
|
|
216
|
+
export function learnFromDispatch(ctx) {
|
|
217
|
+
const outcome = classifyOutcome(ctx.tool, ctx.args, ctx.result, ctx.error);
|
|
218
|
+
if (!outcome)
|
|
219
|
+
return;
|
|
220
|
+
const target = durableTarget(ctx.tool, ctx.args);
|
|
221
|
+
const action = LEARNABLE_ACTIONS[ctx.tool];
|
|
222
|
+
if (!target || !action || !ctx.url)
|
|
223
|
+
return;
|
|
224
|
+
let url;
|
|
225
|
+
try {
|
|
226
|
+
url = new URL(ctx.url);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (url.origin === 'null')
|
|
232
|
+
return;
|
|
233
|
+
if (loadConfig().mapLearningEnabled === false)
|
|
234
|
+
return;
|
|
235
|
+
recordOutcome(getDb(), {
|
|
236
|
+
origin: url.origin,
|
|
237
|
+
pathname: url.pathname,
|
|
238
|
+
target: target.target,
|
|
239
|
+
target_kind: target.target_kind,
|
|
240
|
+
action,
|
|
241
|
+
outcome,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=learning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learning.js","sourceRoot":"","sources":["../../src/map/learning.ts"],"names":[],"mappings":"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"}
|
package/dist/map/queries.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
1
2
|
export type EntryKind = 'selector' | 'flow' | 'gotcha';
|
|
2
3
|
export interface AppRow {
|
|
3
4
|
id: number;
|
|
@@ -30,6 +31,19 @@ export interface UpsertAppInput {
|
|
|
30
31
|
notes?: string | null;
|
|
31
32
|
}
|
|
32
33
|
export declare function upsertApp(input: UpsertAppInput): AppRow;
|
|
34
|
+
/**
|
|
35
|
+
* The app the map learner counts into for an origin, read-only on purpose.
|
|
36
|
+
*
|
|
37
|
+
* Picks the most recently seen app of the canonical origin with the same
|
|
38
|
+
* winner rule `getMapHints` applies (last_seen_at DESC, id DESC), so learned
|
|
39
|
+
* rows land in the app `list_tabs` and `recall` already show. It never
|
|
40
|
+
* touches that app's `last_seen_at` or `title`: a passive learner bumping
|
|
41
|
+
* freshness would reorder which app an origin resolves to. Only when the
|
|
42
|
+
* origin has no app at all does it create one, keyed and titled by the
|
|
43
|
+
* origin host, never by a tab title (titles change with page state, e.g.
|
|
44
|
+
* "Inbox (3) - My App", and would mint a new app per title).
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveLearnerApp(db: Database.Database, origin: string): AppRow;
|
|
33
47
|
export interface SaveEntryInput {
|
|
34
48
|
origin: string;
|
|
35
49
|
app_key?: string | null;
|
|
@@ -84,8 +98,32 @@ export interface RecallResult {
|
|
|
84
98
|
pitfalls: InsightRow[];
|
|
85
99
|
mechanisms: InsightRow[];
|
|
86
100
|
entries: EntryRow[];
|
|
101
|
+
/** What the map learned from use (see learning.ts): failures first, then
|
|
102
|
+
* most used, at most `RECALL_MAX_LEARNED`. Present only when the app has
|
|
103
|
+
* learned rows, so a recall of an app with none reads exactly as before. */
|
|
104
|
+
learned?: LearnedRecallItem[];
|
|
87
105
|
flows: FlowRow[];
|
|
88
106
|
}
|
|
107
|
+
/** One learned target as `list_tabs`' inline map carries it. Targets only:
|
|
108
|
+
* a CSS selector, or a `browser.find` semantic key as JSON. */
|
|
109
|
+
export interface LearnedSummary {
|
|
110
|
+
target: string;
|
|
111
|
+
target_kind: 'selector' | 'key';
|
|
112
|
+
action: string;
|
|
113
|
+
ok_n: number;
|
|
114
|
+
fail_n: number;
|
|
115
|
+
last_reason: string | null;
|
|
116
|
+
}
|
|
117
|
+
/** Recall's richer form: the route it was learned on and when. */
|
|
118
|
+
export interface LearnedRecallItem extends LearnedSummary {
|
|
119
|
+
url_pattern: string;
|
|
120
|
+
last_ok_at: string | null;
|
|
121
|
+
last_fail_at: string | null;
|
|
122
|
+
}
|
|
123
|
+
/** Learned rows of one app in hint order, capped at `limit`. Takes the DB
|
|
124
|
+
* explicitly so the learner's tests can read through the handle they write
|
|
125
|
+
* through. */
|
|
126
|
+
export declare function listLearned(db: Database.Database, app_id: number, limit: number): LearnedRecallItem[];
|
|
89
127
|
export declare function recall(input: RecallInput): RecallResult;
|
|
90
128
|
export interface RecordUseInput {
|
|
91
129
|
entry_id: number;
|
|
@@ -193,6 +231,10 @@ export interface MapHintInline {
|
|
|
193
231
|
description: string | null;
|
|
194
232
|
steps: unknown;
|
|
195
233
|
}[];
|
|
234
|
+
/** Up to 3 learned targets, failures first. Present only when the app has
|
|
235
|
+
* learned rows and at least one fits the byte cap; never counted toward
|
|
236
|
+
* `INLINE_MAX_ITEMS`. */
|
|
237
|
+
learned?: LearnedSummary[];
|
|
196
238
|
}
|
|
197
239
|
export interface MapHint {
|
|
198
240
|
app_key: string;
|
|
@@ -203,6 +245,9 @@ export interface MapHint {
|
|
|
203
245
|
/** Present only when the map is small (see `INLINE_MAX_ITEMS` /
|
|
204
246
|
* `INLINE_MAX_BYTES`); otherwise the counts above are all there is. */
|
|
205
247
|
inline?: MapHintInline;
|
|
248
|
+
/** Learned targets of this app that have failed at least once, present
|
|
249
|
+
* only when there are some and no inline map carrying them fits. */
|
|
250
|
+
learned_failures?: number;
|
|
206
251
|
}
|
|
207
252
|
/**
|
|
208
253
|
* Lean existence-and-count lookup for a tab's origin — powers the optional
|