hunter-mate 0.1.17 → 0.1.19
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/dist/assets/extension/background/{service-worker.20261001153649.js → service-worker.20261008011501.js} +868 -142
- package/dist/assets/extension/background/service-worker.js +868 -142
- package/dist/assets/extension/build-info.json +1 -1
- package/dist/assets/extension/content/control-runtime.js +1028 -0
- package/dist/assets/extension/content/index.js +35 -0
- package/dist/assets/extension/manifest.json +4 -3
- package/dist/assets/extension/offscreen/offscreen.html +1 -1
- package/dist/assets/extension/offscreen/offscreen.js +2 -2
- package/dist/assets/extension/package.json +1 -1
- package/dist/assets/extension/sidepanel/index.css +1 -1
- package/dist/assets/extension/sidepanel/index.html +1 -1
- package/dist/assets/extension/sidepanel/index.js +23 -14
- package/dist/assets/server/main.mjs +10015 -4786
- package/dist/index.js +849 -318
- package/dist/web/dist/404.html +1 -1
- package/dist/web/dist/_next/static/{L4ZJv0swR7OTsvB_PaAB4 → 9Wm8FfXwBJyMk-wYf4l81}/_ssgManifest.js +1 -1
- package/dist/web/dist/_next/static/chunks/542-4a43545450151f1d.js +1 -0
- package/dist/web/dist/_next/static/chunks/670-750aa43678fb0d30.js +1 -0
- package/dist/web/dist/_next/static/chunks/852-5150a7ae95a599fb.js +1 -0
- package/dist/web/dist/_next/static/chunks/{935-bb4adb3868af4cd4.js → 935-4058f12c3d405b32.js} +4 -4
- package/dist/web/dist/_next/static/chunks/app/admin/data/page-a566c6b13c61f9d4.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/companies/page-058047767bc162f4.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/company/admin/page-a2088b59c3820800.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/jobs/page-4fb6a67d044b238d.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/layout-1feb99bf08beb3c8.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/matching/jobs/[id]/talents/page-c6714cec1ffd7c5e.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/matching/portraits/[id]/jobs/page-658eca1ae5ca29dc.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/page-291cd145a56bd020.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/parselog/page-b1b7ae9483273ce0.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/recordings/page-c12948f01885099e.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/resource-usage/{page-3b8bd27536a7889a.js → page-8394e96892688180.js} +1 -1
- package/dist/web/dist/_next/static/chunks/app/shared/[token]/page-e859e87f2301b120.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/snapshots/[id]/agent-prompt/page-f519552be54e3bdf.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/talents/page-75af05473ea322d9.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/team/insights/page-7b8769e35cd9987b.js +1 -0
- package/dist/web/dist/_next/static/chunks/{main-ead3ac50aa4bc931.js → main-8d45182e1c21cb95.js} +1 -1
- package/dist/web/dist/_next/static/chunks/{main-app-d053d37cc3b76ace.js → main-app-70da092dee991648.js} +1 -1
- package/dist/web/dist/_next/static/chunks/pages/{_app-e11d02a41d9f3cb9.js → _app-8438560f1c1a7980.js} +3 -3
- package/dist/web/dist/_next/static/css/9eaf0c9cc1c24641.css +3 -0
- package/dist/web/dist/admin/data.html +1 -1
- package/dist/web/dist/admin/data.txt +3 -3
- package/dist/web/dist/auth/verify.html +1 -1
- package/dist/web/dist/auth/verify.txt +2 -2
- package/dist/web/dist/companies.html +1 -1
- package/dist/web/dist/companies.txt +7 -6
- package/dist/web/dist/company/admin.html +1 -1
- package/dist/web/dist/company/admin.txt +3 -3
- package/dist/web/dist/entity.html +1 -1
- package/dist/web/dist/entity.txt +2 -2
- package/dist/web/dist/index.html +1 -1
- package/dist/web/dist/index.txt +3 -3
- package/dist/web/dist/jobs.html +1 -1
- package/dist/web/dist/jobs.txt +3 -3
- package/dist/web/dist/logs.html +1 -1
- package/dist/web/dist/logs.txt +2 -2
- package/dist/web/dist/matching/jobs/placeholder/talents.html +1 -1
- package/dist/web/dist/matching/jobs/placeholder/talents.txt +3 -3
- package/dist/web/dist/matching/portraits/placeholder/jobs.html +1 -1
- package/dist/web/dist/matching/portraits/placeholder/jobs.txt +3 -3
- package/dist/web/dist/parselog.html +1 -1
- package/dist/web/dist/parselog.txt +3 -3
- package/dist/web/dist/recordings.html +1 -1
- package/dist/web/dist/recordings.txt +3 -3
- package/dist/web/dist/resource-usage.html +1 -1
- package/dist/web/dist/resource-usage.txt +3 -3
- package/dist/web/dist/shared/placeholder.html +1 -0
- package/dist/web/dist/shared/placeholder.txt +8 -0
- package/dist/web/dist/snapshots/placeholder/agent-prompt.html +1 -1
- package/dist/web/dist/snapshots/placeholder/agent-prompt.txt +3 -3
- package/dist/web/dist/talents.html +1 -1
- package/dist/web/dist/talents.txt +3 -3
- package/dist/web/dist/team/insights.html +1 -0
- package/dist/web/dist/team/insights.txt +7 -0
- package/package.json +1 -1
- package/dist/web/dist/_next/static/chunks/170-b66b6f731381943e.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/admin/data/page-37e01dad70306bf3.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/companies/page-985002eea5b295d0.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/company/admin/page-8c7ecc92bef09e92.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/jobs/page-e39b3e25f0a81254.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/layout-d094dc654772bf32.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/matching/jobs/[id]/talents/page-936f6190c968d20b.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/matching/portraits/[id]/jobs/page-ea44a2236bd53cae.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/page-41d8e008a9411221.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/parselog/page-3f756bbd580f337e.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/recordings/page-ffd507b12e67f2db.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/snapshots/[id]/agent-prompt/page-c89b32aa2ef509f5.js +0 -1
- package/dist/web/dist/_next/static/chunks/app/talents/page-8e0b4072df138fa3.js +0 -1
- package/dist/web/dist/_next/static/css/1dd88e02ef1af7b9.css +0 -3
- /package/dist/web/dist/_next/static/{L4ZJv0swR7OTsvB_PaAB4 → 9Wm8FfXwBJyMk-wYf4l81}/_buildManifest.js +0 -0
|
@@ -1,3 +1,325 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __esm = (fn, res) => function __init() {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
};
|
|
10
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
11
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
12
|
+
};
|
|
13
|
+
var __export = (target, all) => {
|
|
14
|
+
for (var name in all)
|
|
15
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
+
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
30
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
|
+
mod
|
|
32
|
+
));
|
|
33
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
34
|
+
|
|
35
|
+
// ../shared/dist/types.js
|
|
36
|
+
var require_types = __commonJS({
|
|
37
|
+
"../shared/dist/types.js"(exports) {
|
|
38
|
+
"use strict";
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ../../node_modules/.pnpm/@chromepilot+kit@0.1.8_@sentry+node@10.70.0_@opentelemetry+core@2.10.0_@opentelemetry+api@1.9.1__/node_modules/@chromepilot/kit/dist/redactor.js
|
|
44
|
+
var redactor_exports = {};
|
|
45
|
+
__export(redactor_exports, {
|
|
46
|
+
DEFAULT_HEADER_KEYS: () => DEFAULT_HEADER_KEYS,
|
|
47
|
+
DEFAULT_QUERY_KEYS: () => DEFAULT_QUERY_KEYS,
|
|
48
|
+
createRedactor: () => createRedactor
|
|
49
|
+
});
|
|
50
|
+
function createRedactor(opts = {}) {
|
|
51
|
+
const placeholder = opts.placeholder ?? "[REDACTED]";
|
|
52
|
+
const matchMode = opts.matchMode ?? "exact";
|
|
53
|
+
const headerKeys = [
|
|
54
|
+
...opts.useDefaultHeaderKeys === false ? [] : DEFAULT_HEADER_KEYS,
|
|
55
|
+
...opts.sensitiveHeaderKeys ?? []
|
|
56
|
+
];
|
|
57
|
+
const queryKeys = [
|
|
58
|
+
...opts.useDefaultQueryKeys === false ? [] : DEFAULT_QUERY_KEYS,
|
|
59
|
+
...opts.sensitiveQueryKeys ?? []
|
|
60
|
+
];
|
|
61
|
+
const bodyMode = opts.bodyMode ?? "json";
|
|
62
|
+
function hit(keys, key) {
|
|
63
|
+
const lower = key.toLowerCase();
|
|
64
|
+
return matchMode === "includes" ? keys.some((s) => lower.includes(s)) : keys.includes(lower);
|
|
65
|
+
}
|
|
66
|
+
function redactHeaders(headers) {
|
|
67
|
+
if (!headers)
|
|
68
|
+
return void 0;
|
|
69
|
+
const result = {};
|
|
70
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
71
|
+
result[key] = hit(headerKeys, key) ? placeholder : value;
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
function redactUrlQuery(url) {
|
|
76
|
+
try {
|
|
77
|
+
const u = new URL(url);
|
|
78
|
+
let changed = false;
|
|
79
|
+
for (const key of u.searchParams.keys()) {
|
|
80
|
+
if (hit(queryKeys, key)) {
|
|
81
|
+
u.searchParams.set(key, placeholder);
|
|
82
|
+
changed = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return changed ? u.toString() : url;
|
|
86
|
+
} catch {
|
|
87
|
+
return url;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function redactBodyJson(body) {
|
|
91
|
+
const patterns = [
|
|
92
|
+
/"password"\s*:\s*"[^"]*"/gi,
|
|
93
|
+
/"access_token"\s*:\s*"[^"]*"/gi,
|
|
94
|
+
/"refresh_token"\s*:\s*"[^"]*"/gi,
|
|
95
|
+
/"api_key"\s*:\s*"[^"]*"/gi,
|
|
96
|
+
/"token"\s*:\s*"[^"]*"/gi
|
|
97
|
+
];
|
|
98
|
+
let result = body;
|
|
99
|
+
for (const p of patterns) {
|
|
100
|
+
result = result.replace(p, (match) => {
|
|
101
|
+
const key = match.split(":")[0];
|
|
102
|
+
return `${key}: "${placeholder}"`;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
function redactBodyLoose(body) {
|
|
108
|
+
let result = body.replace(/(password|token|secret|api[_-]?key)\s*[:=]\s*[^\s&]+/gi, `$1=${placeholder}`);
|
|
109
|
+
for (const [pattern, replacement] of [PHONE_RULE, EMAIL_RULE, ...opts.extraBodyRules ?? []]) {
|
|
110
|
+
result = result.replace(pattern, replacement);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
function redactBody(body) {
|
|
115
|
+
if (!body)
|
|
116
|
+
return void 0;
|
|
117
|
+
return bodyMode === "loose" ? redactBodyLoose(body) : redactBodyJson(body);
|
|
118
|
+
}
|
|
119
|
+
function redactText(text) {
|
|
120
|
+
let result = text.replace(PHONE_RULE[0], PHONE_RULE[1]).replace(EMAIL_RULE[0], EMAIL_RULE[1]);
|
|
121
|
+
for (const [pattern, replacement] of opts.extraBodyRules ?? []) {
|
|
122
|
+
result = result.replace(pattern, replacement);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
return { redactHeaders, redactUrlQuery, redactBody, redactText };
|
|
127
|
+
}
|
|
128
|
+
var DEFAULT_HEADER_KEYS, DEFAULT_QUERY_KEYS, PHONE_RULE, EMAIL_RULE;
|
|
129
|
+
var init_redactor = __esm({
|
|
130
|
+
"../../node_modules/.pnpm/@chromepilot+kit@0.1.8_@sentry+node@10.70.0_@opentelemetry+core@2.10.0_@opentelemetry+api@1.9.1__/node_modules/@chromepilot/kit/dist/redactor.js"() {
|
|
131
|
+
DEFAULT_HEADER_KEYS = [
|
|
132
|
+
"authorization",
|
|
133
|
+
"cookie",
|
|
134
|
+
"set-cookie",
|
|
135
|
+
"x-api-key",
|
|
136
|
+
"x-auth-token",
|
|
137
|
+
"access_token",
|
|
138
|
+
"refresh_token",
|
|
139
|
+
"password",
|
|
140
|
+
"api_key",
|
|
141
|
+
"apikey",
|
|
142
|
+
"secret",
|
|
143
|
+
"token"
|
|
144
|
+
];
|
|
145
|
+
DEFAULT_QUERY_KEYS = [
|
|
146
|
+
"access_token",
|
|
147
|
+
"refresh_token",
|
|
148
|
+
"token",
|
|
149
|
+
"api_key",
|
|
150
|
+
"apikey",
|
|
151
|
+
"key",
|
|
152
|
+
"password",
|
|
153
|
+
"secret",
|
|
154
|
+
"code"
|
|
155
|
+
];
|
|
156
|
+
PHONE_RULE = [/\b\d{11}\b/g, "***PHONE***"];
|
|
157
|
+
EMAIL_RULE = [
|
|
158
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
159
|
+
"***EMAIL***"
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ../shared/dist/redactor.js
|
|
165
|
+
var require_redactor = __commonJS({
|
|
166
|
+
"../shared/dist/redactor.js"(exports) {
|
|
167
|
+
"use strict";
|
|
168
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
169
|
+
exports.redactText = exports.redactBody = exports.redactUrlQuery = exports.redactHeaders = void 0;
|
|
170
|
+
var redactor_1 = (init_redactor(), __toCommonJS(redactor_exports));
|
|
171
|
+
var redactor = (0, redactor_1.createRedactor)({
|
|
172
|
+
placeholder: "***",
|
|
173
|
+
matchMode: "includes",
|
|
174
|
+
bodyMode: "loose",
|
|
175
|
+
// 完全替换 kit 默认清单:保持 hunter-mate 原有行为
|
|
176
|
+
// (kit 默认 query 清单含 'key',includes 模式会误伤 'monkey' 之类子串)
|
|
177
|
+
useDefaultHeaderKeys: false,
|
|
178
|
+
sensitiveHeaderKeys: [
|
|
179
|
+
"authorization",
|
|
180
|
+
"cookie",
|
|
181
|
+
"x-auth-token",
|
|
182
|
+
"token",
|
|
183
|
+
"api-key",
|
|
184
|
+
"apikey",
|
|
185
|
+
"password",
|
|
186
|
+
"secret"
|
|
187
|
+
],
|
|
188
|
+
useDefaultQueryKeys: false,
|
|
189
|
+
sensitiveQueryKeys: [
|
|
190
|
+
"token",
|
|
191
|
+
"access_token",
|
|
192
|
+
"refresh_token",
|
|
193
|
+
"api_key",
|
|
194
|
+
"apikey",
|
|
195
|
+
"password",
|
|
196
|
+
"secret",
|
|
197
|
+
"code"
|
|
198
|
+
]
|
|
199
|
+
});
|
|
200
|
+
exports.redactHeaders = redactor.redactHeaders;
|
|
201
|
+
exports.redactUrlQuery = redactor.redactUrlQuery;
|
|
202
|
+
exports.redactBody = redactor.redactBody;
|
|
203
|
+
exports.redactText = redactor.redactText;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ../shared/dist/hbr.js
|
|
208
|
+
var require_hbr = __commonJS({
|
|
209
|
+
"../shared/dist/hbr.js"(exports) {
|
|
210
|
+
"use strict";
|
|
211
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
212
|
+
if (k2 === void 0) k2 = k;
|
|
213
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
214
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
215
|
+
desc = { enumerable: true, get: function() {
|
|
216
|
+
return m[k];
|
|
217
|
+
} };
|
|
218
|
+
}
|
|
219
|
+
Object.defineProperty(o, k2, desc);
|
|
220
|
+
} : function(o, m, k, k2) {
|
|
221
|
+
if (k2 === void 0) k2 = k;
|
|
222
|
+
o[k2] = m[k];
|
|
223
|
+
});
|
|
224
|
+
var __exportStar = exports && exports.__exportStar || function(m, exports2) {
|
|
225
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
|
|
226
|
+
};
|
|
227
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
228
|
+
exports.createHbrEvent = createHbrEvent;
|
|
229
|
+
exports.createNetworkEvent = createNetworkEvent;
|
|
230
|
+
exports.createConsoleEvent = createConsoleEvent;
|
|
231
|
+
exports.createResumeEvent = createResumeEvent;
|
|
232
|
+
exports.createActionEvent = createActionEvent;
|
|
233
|
+
function createHbrEvent(type, sessionId, payload, options) {
|
|
234
|
+
return {
|
|
235
|
+
type,
|
|
236
|
+
sessionId,
|
|
237
|
+
timestamp: options?.timestamp ?? Date.now(),
|
|
238
|
+
privacy: options?.privacy ?? "public",
|
|
239
|
+
payload,
|
|
240
|
+
sourceUrl: options?.sourceUrl,
|
|
241
|
+
tabId: options?.tabId
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function createNetworkEvent(sessionId, data, privacy = "redacted") {
|
|
245
|
+
return createHbrEvent("network", sessionId, data, { privacy });
|
|
246
|
+
}
|
|
247
|
+
function createConsoleEvent(sessionId, data, privacy = "private") {
|
|
248
|
+
return createHbrEvent("console", sessionId, data, { privacy });
|
|
249
|
+
}
|
|
250
|
+
function createResumeEvent(sessionId, data, privacy = "private") {
|
|
251
|
+
return createHbrEvent("resume", sessionId, data, { privacy });
|
|
252
|
+
}
|
|
253
|
+
function createActionEvent(sessionId, data, privacy = "public") {
|
|
254
|
+
return createHbrEvent("action", sessionId, data, { privacy });
|
|
255
|
+
}
|
|
256
|
+
__exportStar(require_types(), exports);
|
|
257
|
+
__exportStar(require_redactor(), exports);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ../shared/dist/internal-url.js
|
|
262
|
+
var require_internal_url = __commonJS({
|
|
263
|
+
"../shared/dist/internal-url.js"(exports) {
|
|
264
|
+
"use strict";
|
|
265
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
266
|
+
exports.isHarnessInternalHostname = isHarnessInternalHostname2;
|
|
267
|
+
exports.isHarnessInternalUrl = isHarnessInternalUrl;
|
|
268
|
+
var INTERNAL_HOST_PATTERNS = [
|
|
269
|
+
"dev-ops.hiredchina.com"
|
|
270
|
+
];
|
|
271
|
+
function isHarnessInternalHostname2(hostname) {
|
|
272
|
+
const host = (hostname || "").toLowerCase().replace(/\.$/, "");
|
|
273
|
+
if (!host)
|
|
274
|
+
return false;
|
|
275
|
+
return INTERNAL_HOST_PATTERNS.some((p) => {
|
|
276
|
+
if (p.startsWith("*.")) {
|
|
277
|
+
const root = p.slice(2);
|
|
278
|
+
return host === root || host.endsWith(`.${root}`);
|
|
279
|
+
}
|
|
280
|
+
return host === p;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
function isHarnessInternalUrl(url) {
|
|
284
|
+
if (!url)
|
|
285
|
+
return false;
|
|
286
|
+
try {
|
|
287
|
+
return isHarnessInternalHostname2(new URL(url).hostname);
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// ../shared/dist/index.js
|
|
296
|
+
var require_dist = __commonJS({
|
|
297
|
+
"../shared/dist/index.js"(exports) {
|
|
298
|
+
"use strict";
|
|
299
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
300
|
+
if (k2 === void 0) k2 = k;
|
|
301
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
302
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
303
|
+
desc = { enumerable: true, get: function() {
|
|
304
|
+
return m[k];
|
|
305
|
+
} };
|
|
306
|
+
}
|
|
307
|
+
Object.defineProperty(o, k2, desc);
|
|
308
|
+
} : function(o, m, k, k2) {
|
|
309
|
+
if (k2 === void 0) k2 = k;
|
|
310
|
+
o[k2] = m[k];
|
|
311
|
+
});
|
|
312
|
+
var __exportStar = exports && exports.__exportStar || function(m, exports2) {
|
|
313
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
|
|
314
|
+
};
|
|
315
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
316
|
+
__exportStar(require_types(), exports);
|
|
317
|
+
__exportStar(require_redactor(), exports);
|
|
318
|
+
__exportStar(require_hbr(), exports);
|
|
319
|
+
__exportStar(require_internal_url(), exports);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
1
323
|
// src/shared/messaging.ts
|
|
2
324
|
var SERVER_PORT = 34571;
|
|
3
325
|
var WS_URL = `ws://localhost:${SERVER_PORT}/ws`;
|
|
@@ -571,6 +893,27 @@ function runControl(command, args) {
|
|
|
571
893
|
if ("errorEnvelope" in r) return r.errorEnvelope;
|
|
572
894
|
const { el, matches_n, match_level, target } = r;
|
|
573
895
|
el.scrollIntoView({ block: "center" });
|
|
896
|
+
if (a.full === true) {
|
|
897
|
+
const rect = el.getBoundingClientRect();
|
|
898
|
+
const opts = {
|
|
899
|
+
bubbles: true,
|
|
900
|
+
cancelable: true,
|
|
901
|
+
view: window,
|
|
902
|
+
clientX: rect.x + rect.width / 2,
|
|
903
|
+
clientY: rect.y + rect.height / 2,
|
|
904
|
+
button: 0,
|
|
905
|
+
buttons: 1,
|
|
906
|
+
pointerId: 1,
|
|
907
|
+
pointerType: "mouse",
|
|
908
|
+
isPrimary: true
|
|
909
|
+
};
|
|
910
|
+
el.dispatchEvent(new PointerEvent("pointerdown", opts));
|
|
911
|
+
el.dispatchEvent(new MouseEvent("mousedown", opts));
|
|
912
|
+
el.dispatchEvent(new PointerEvent("pointerup", { ...opts, buttons: 0 }));
|
|
913
|
+
el.dispatchEvent(new MouseEvent("mouseup", { ...opts, buttons: 0 }));
|
|
914
|
+
el.dispatchEvent(new MouseEvent("click", { ...opts, buttons: 0 }));
|
|
915
|
+
return { clicked: true, full: true, target, matches_n, match_level, tag: el.tagName.toLowerCase(), text: getVisibleText(el) };
|
|
916
|
+
}
|
|
574
917
|
el.click();
|
|
575
918
|
return { clicked: true, target, matches_n, match_level, tag: el.tagName.toLowerCase(), text: getVisibleText(el) };
|
|
576
919
|
}
|
|
@@ -999,10 +1342,13 @@ globalThis.__cpRunControl = runControl;
|
|
|
999
1342
|
globalThis.__cpRuntimeVersion = RUNTIME_VERSION;
|
|
1000
1343
|
|
|
1001
1344
|
// src/generated/control-runtime-source.ts
|
|
1002
|
-
var control_runtime_source_default = '"use strict";\n(() => {\n // src/shared/control-runtime.ts\n function getRefStore() {\n const g = globalThis;\n if (!g.__cpRefs) {\n g.__cpRefs = { counter: 1, refs: /* @__PURE__ */ new Map(), cssPaths: /* @__PURE__ */ new Map() };\n }\n return g.__cpRefs;\n }\n function allocRef(el) {\n const store = getRefStore();\n for (const [ref2, existing] of store.refs) {\n if (existing === el) return ref2;\n }\n const ref = store.counter++;\n store.refs.set(ref, el);\n store.cssPaths.set(ref, uniqueCssPath(el));\n return ref;\n }\n function uniqueCssPath(el) {\n if (el.id) return `#${CSS.escape(el.id)}`;\n const parts = [];\n let cur = el;\n while (cur && cur !== document.body && cur !== document.documentElement) {\n let sel = cur.tagName.toLowerCase();\n if (cur.id) {\n parts.unshift(`#${CSS.escape(cur.id)}`);\n break;\n }\n const parent = cur.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);\n if (sameTag.length > 1) {\n const idx = sameTag.indexOf(cur) + 1;\n sel += `:nth-of-type(${idx})`;\n }\n }\n parts.unshift(sel);\n cur = parent;\n }\n return parts.join(" > ");\n }\n function err(code, message, extra) {\n return { error: { code, message, ...extra || {} } };\n }\n var ERROR_CODES = {\n NOT_FOUND: "not_found",\n STALE_REF: "stale_ref",\n INVALID_SELECTOR: "invalid_selector",\n SELECTOR_NOT_FOUND: "selector_not_found",\n SELECTOR_AMBIGUOUS: "selector_ambiguous",\n SELECTOR_NTH_OUT_OF_RANGE: "selector_nth_out_of_range",\n OPTION_NOT_FOUND: "option_not_found",\n NOT_A_SELECT: "not_a_select"\n };\n var IMPLICIT_ROLE = {\n a: "link",\n button: "button",\n select: "combobox",\n textarea: "textbox",\n h1: "heading",\n h2: "heading",\n h3: "heading",\n h4: "heading",\n h5: "heading",\n h6: "heading",\n img: "img",\n nav: "navigation",\n ul: "list",\n ol: "list",\n li: "listitem",\n main: "main",\n article: "article",\n aside: "complementary",\n header: "banner",\n footer: "contentinfo",\n form: "form",\n table: "table",\n summary: "button",\n option: "option",\n output: "status"\n };\n function roleOf(el) {\n const explicit = el.getAttribute("role");\n if (explicit) return explicit.split(/\\s+/)[0];\n const tag = el.tagName.toLowerCase();\n if (tag === "input") {\n const type = el.type || "text";\n if (type === "hidden") return null;\n if (type === "checkbox") return "checkbox";\n if (type === "radio") return "radio";\n if (type === "button" || type === "submit" || type === "reset") return "button";\n if (type === "range") return "slider";\n return "textbox";\n }\n if (tag === "progress") return "progressbar";\n if (tag === "meter") return "meter";\n return IMPLICIT_ROLE[tag] ?? null;\n }\n function accessibleName(el) {\n const aria = el.getAttribute("aria-label");\n if (aria) return aria.trim();\n const labelledby = el.getAttribute("aria-labelledby");\n if (labelledby) {\n const parts = labelledby.split(/\\s+/).map((id2) => document.getElementById(id2)?.textContent?.trim() || "");\n const joined = parts.filter(Boolean).join(" ");\n if (joined) return joined.slice(0, 120);\n }\n const id = el.getAttribute("id");\n if (id) {\n const label = document.querySelector(`label[for="${CSS.escape(id)}"]`);\n if (label?.textContent?.trim()) return label.textContent.trim().slice(0, 120);\n }\n if (el.tagName.toLowerCase() === "input") {\n const type = el.type;\n if (type === "button" || type === "submit" || type === "reset") {\n const v = el.value;\n if (v) return v;\n }\n }\n const alt = el.getAttribute("alt");\n if (alt) return alt;\n const title = el.getAttribute("title");\n if (title) return title;\n const placeholder = el.getAttribute("placeholder");\n if (placeholder) return placeholder;\n return (el.textContent || "").replace(/\\s+/g, " ").trim().slice(0, 120);\n }\n function isVisible(el) {\n if (!(el instanceof HTMLElement) && !(el instanceof SVGElement)) return false;\n const html = el;\n if (html.hasAttribute("hidden")) return false;\n if (html.closest("[hidden]")) return false;\n const style = window.getComputedStyle(html);\n if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;\n if (el instanceof HTMLElement && el.offsetParent === null) {\n const pos = style.position;\n if (pos !== "fixed" && pos !== "sticky") return false;\n }\n return true;\n }\n var INTERACTIVE_SELECTOR = "a[href], button, input, textarea, select, summary, [role], [contenteditable=\'\'], [contenteditable=\'true\'], [onclick], [tabindex]";\n function isInteractive(el) {\n return el.matches(INTERACTIVE_SELECTOR);\n }\n function testidSelector(testid) {\n return `[data-testid="${CSS.escape(testid)}"], [data-test-id="${CSS.escape(testid)}"], [data-test="${CSS.escape(testid)}"], [data-cy="${CSS.escape(testid)}"]`;\n }\n function resolveTargetRich(sel) {\n const targetDesc = sel.ref != null ? String(sel.ref) : sel.css ?? (sel.role || sel.name || sel.label || sel.testid || sel.text ? `${sel.role ? `role=${sel.role} ` : ""}${sel.name ? `name=${sel.name} ` : ""}${sel.label ? `label=${sel.label} ` : ""}${sel.testid ? `testid=${sel.testid} ` : ""}${sel.text ? `text=${sel.text}` : ""}`.trim() : "");\n if (sel.ref != null) {\n const store = getRefStore();\n const el = store.refs.get(sel.ref);\n if (el && document.contains(el) && isVisible(el)) {\n return { el, matches_n: 1, match_level: "exact", target: targetDesc };\n }\n if (el && !document.contains(el)) {\n const cssPath = store.cssPaths.get(sel.ref);\n if (cssPath) {\n const re = document.querySelector(cssPath);\n if (re && isVisible(re)) {\n store.refs.set(sel.ref, re);\n return { el: re, matches_n: 1, match_level: "reidentified", target: targetDesc };\n }\n }\n return { errorEnvelope: err(ERROR_CODES.STALE_REF, "Numeric ref is no longer in the DOM. Re-run state.") };\n }\n return { errorEnvelope: err(ERROR_CODES.NOT_FOUND, "Numeric ref is no longer in the DOM. Re-run state.") };\n }\n let candidates = [];\n if (sel.css) {\n try {\n candidates = Array.from(document.querySelectorAll(sel.css));\n } catch {\n return { errorEnvelope: err(ERROR_CODES.INVALID_SELECTOR, "CSS was rejected by querySelectorAll.", { selector: sel.css }) };\n }\n if (candidates.length === 0) {\n return { errorEnvelope: err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: sel.css, candidates: suggestCandidates() }) };\n }\n } else if (sel.testid) {\n try {\n candidates = Array.from(document.querySelectorAll(testidSelector(sel.testid)));\n } catch {\n return { errorEnvelope: err(ERROR_CODES.INVALID_SELECTOR, "Invalid testid.", { testid: sel.testid }) };\n }\n } else if (sel.role || sel.name || sel.label || sel.text) {\n const wantRole = sel.role ? sel.role.toLowerCase() : null;\n const wantName = sel.name ? sel.name.toLowerCase() : null;\n const wantText = sel.text ? sel.text.toLowerCase() : null;\n let labelTargets = [];\n if (sel.label) {\n const lower = sel.label.toLowerCase();\n labelTargets = Array.from(document.querySelectorAll("label")).filter((l) => (l.textContent || "").trim().toLowerCase().includes(lower)).map((l) => {\n const forId = l.getAttribute("for");\n if (forId) return document.getElementById(forId);\n return l.querySelector("input, select, textarea");\n }).filter((x) => !!x);\n }\n const all = Array.from(document.querySelectorAll("*"));\n candidates = all.filter((el) => {\n if (!isVisible(el)) return false;\n const role = roleOf(el);\n if (wantRole && (!role || role.toLowerCase() !== wantRole)) return false;\n if (wantName) {\n const name = accessibleName(el).toLowerCase();\n if (!name.includes(wantName)) return false;\n }\n if (wantText) {\n const t = (el.textContent || "").replace(/\\s+/g, " ").trim().toLowerCase();\n if (!t.includes(wantText)) return false;\n }\n return true;\n });\n if (labelTargets.length) {\n const set = new Set(labelTargets);\n candidates = labelTargets.concat(candidates.filter((c) => !set.has(c)));\n }\n if (candidates.length === 0) {\n return {\n errorEnvelope: err(\n ERROR_CODES.SELECTOR_NOT_FOUND,\n "Semantic locator matches 0 elements.",\n { role: sel.role, name: sel.name, label: sel.label, text: sel.text, candidates: suggestCandidates() }\n )\n };\n }\n } else {\n return { errorEnvelope: err("no_target", "Provide a numeric ref, CSS selector, or semantic locator (--role/--name/--label/--text/--testid).") };\n }\n const visible = candidates.filter(isVisible);\n const pool = visible.length ? visible : candidates;\n if (sel.nth != null) {\n if (sel.nth < 0 || sel.nth >= pool.length) {\n return { errorEnvelope: err(ERROR_CODES.SELECTOR_NTH_OUT_OF_RANGE, `--nth ${sel.nth} beyond match count ${pool.length}.`) };\n }\n const el = pool[sel.nth];\n return { el, matches_n: pool.length, match_level: "reidentified", target: targetDesc };\n }\n if (pool.length > 1) {\n return {\n errorEnvelope: err(ERROR_CODES.SELECTOR_AMBIGUOUS, `CSS matches ${pool.length} elements and no --nth given.`, {\n matches_n: pool.length,\n candidates: pool.slice(0, 5).map((el) => uniqueCssPath(el))\n })\n };\n }\n return { el: pool[0], matches_n: 1, match_level: "reidentified", target: targetDesc };\n }\n function suggestCandidates() {\n const store = getRefStore();\n const recent = Array.from(store.refs.entries()).slice(-5).map(([, el]) => uniqueCssPath(el));\n return recent;\n }\n function compoundOf(el) {\n const tag = el.tagName.toLowerCase();\n if (tag === "input") {\n const input = el;\n const type = input.type;\n if (["date", "time", "datetime-local", "month", "week"].includes(type)) {\n const formatMap = {\n date: "YYYY-MM-DD",\n time: "HH:mm",\n "datetime-local": "YYYY-MM-DDTHH:mm",\n month: "YYYY-MM",\n week: "YYYY-Www"\n };\n return {\n control: type,\n format: formatMap[type] ?? type,\n current: input.value || "",\n min: input.min || null,\n max: input.max || null\n };\n }\n if (type === "file") {\n const current = Array.from(input.files ?? []).map((f) => f.name);\n return { control: "file", multiple: input.multiple, current, accept: input.accept || null };\n }\n return null;\n }\n if (tag === "select") {\n const select = el;\n const options = Array.from(select.options).slice(0, 50).map((o) => ({\n label: (o.textContent || "").trim(),\n value: o.value,\n selected: o.selected\n }));\n return {\n control: "select",\n multiple: select.multiple,\n current: select.selectedOptions[0] ? (select.selectedOptions[0].textContent || "").trim() : "",\n options,\n options_total: select.options.length\n };\n }\n return null;\n }\n var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "meta", "link", "head", "base", "template", "path", "circle", "rect", "g", "defs", "polygon", "line"]);\n function describeElement(el, ref) {\n const tag = el.tagName.toLowerCase();\n const role = el.getAttribute("role") ?? null;\n const name = accessibleName(el);\n const parts = [];\n if (ref != null) parts.push(`[${ref}]`);\n parts.push(tag);\n if (role && role !== IMPLICIT_ROLE[tag]) parts.push(`role=${role}`);\n if (tag === "input") {\n const input = el;\n if (input.type && input.type !== "text") parts.push(`[${input.type}]`);\n if (input.disabled) parts.push("(disabled)");\n }\n if (name) parts.push(JSON.stringify(name.length > 60 ? name.slice(0, 57) + "\\u2026" : name));\n if (tag === "a") {\n const href = el.getAttribute("href");\n if (href) parts.push(`\\u2192 ${href.slice(0, 50)}`);\n }\n if (el.disabled) parts.push("(disabled)");\n return parts.join(" ");\n }\n function buildStateTree() {\n const lines = [];\n const compounds = {};\n const budget = { nodes: 0, chars: 0, truncated: false };\n const MAX_NODES = 400;\n const MAX_CHARS = 9e3;\n let hiddenInteractive = 0;\n function emit(depth, text) {\n const line = " ".repeat(Math.min(depth, 12)) + text;\n lines.push(line);\n budget.chars += line.length;\n }\n function walk(el, depth) {\n if (budget.nodes >= MAX_NODES || budget.chars >= MAX_CHARS) {\n budget.truncated = true;\n return;\n }\n const tag = el.tagName.toLowerCase();\n if (SKIP_TAGS.has(tag)) return;\n budget.nodes++;\n const visible = isVisible(el);\n const interactive = isInteractive(el);\n if (interactive) {\n if (visible) {\n const ref = allocRef(el);\n emit(depth, describeElement(el, ref));\n const compound = compoundOf(el);\n if (compound) compounds[String(ref)] = compound;\n } else {\n hiddenInteractive++;\n return;\n }\n } else if (visible) {\n if (/^h[1-6]$/.test(tag)) {\n const t = accessibleName(el);\n if (t) emit(depth, `${"#".repeat(Number(tag[1]))} ${t}`);\n } else if (tag === "p" || tag === "li" || tag === "td" || tag === "th" || tag === "figcaption" || tag === "blockquote") {\n const ownText = ownTextOf(el);\n if (ownText) emit(depth, ownText.slice(0, 100));\n } else if (tag === "img") {\n const alt = el.getAttribute("alt") || "";\n if (alt) emit(depth, `img ${JSON.stringify(alt.slice(0, 60))}`);\n }\n }\n if (depth < 14) {\n for (const child of Array.from(el.children)) {\n walk(child, depth + 1);\n if (budget.truncated) return;\n }\n }\n }\n function ownTextOf(el) {\n let text = "";\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) text += node.textContent || "";\n }\n return text.replace(/\\s+/g, " ").trim();\n }\n const root = document.querySelector("main") || document.querySelector("#root") || document.body;\n emit(0, `# ${document.title}`);\n walk(root, 0);\n const scrollable = document.documentElement.scrollHeight > window.innerHeight + 200;\n if (scrollable) {\n emit(0, "\\u2195 page is scrollable \\u2014 run cp.scroll down to reveal more");\n }\n if (hiddenInteractive > 0) {\n emit(0, `\\u2139 ${hiddenInteractive} hidden interactive element(s) not tagged`);\n }\n if (budget.truncated) {\n emit(0, "\\u2026 (snapshot truncated by budget \\u2014 narrow with a selector or scroll)");\n }\n return {\n url: location.href,\n title: document.title,\n text: lines.join("\\n"),\n compounds,\n hints: { scroll: scrollable, hidden_interactive: hiddenInteractive },\n truncated: budget.truncated\n };\n }\n function runControl(command, args) {\n const a = args || {};\n function getVisibleText(el) {\n return (el.textContent || "").trim().replace(/\\s+/g, " ").slice(0, 120);\n }\n function getAttrs(el) {\n const attrs = {};\n for (const name of ["id", "class", "name", "type", "href", "src", "placeholder", "value", "role", "aria-label", "data-testid"]) {\n attrs[name] = el.getAttribute(name);\n }\n return attrs;\n }\n function sel() {\n return {\n ref: a.ref,\n css: a.css,\n nth: a.nth,\n role: a.role,\n name: a.name,\n label: a.label,\n text: a.text,\n testid: a.testid\n };\n }\n function findElements(findArgs) {\n const limit = findArgs.limit ?? 20;\n const textMax = findArgs["text-max"] ?? 120;\n let elements = [];\n if (findArgs.css) {\n try {\n elements = Array.from(document.querySelectorAll(findArgs.css));\n } catch {\n return err(ERROR_CODES.INVALID_SELECTOR, "CSS was rejected by querySelectorAll.", { selector: findArgs.css });\n }\n } else if (findArgs.testid) {\n try {\n elements = Array.from(document.querySelectorAll(testidSelector(findArgs.testid)));\n } catch {\n return err(ERROR_CODES.INVALID_SELECTOR, "Invalid testid.");\n }\n } else if (findArgs.role || findArgs.name || findArgs.label) {\n const r = resolveTargetRich({ role: findArgs.role, name: findArgs.name, label: findArgs.label, text: findArgs.text });\n if ("errorEnvelope" in r) return r.errorEnvelope;\n elements = document.querySelectorAll("*").length ? semanticCandidates(findArgs) : [];\n } else if (findArgs.text) {\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);\n const lower = findArgs.text.toLowerCase();\n const seen = /* @__PURE__ */ new Set();\n let node;\n while (node = walker.nextNode()) {\n if (node.textContent?.toLowerCase().includes(lower)) {\n const el = node.parentElement;\n if (el && !seen.has(el)) {\n seen.add(el);\n elements.push(el);\n if (elements.length >= limit) break;\n }\n }\n }\n } else {\n elements = Array.from(document.querySelectorAll(INTERACTIVE_SELECTOR));\n }\n const out = elements.slice(0, limit).map((el, i) => {\n const ref = allocRef(el);\n return {\n nth: i,\n ref,\n tag: el.tagName.toLowerCase(),\n role: roleOf(el),\n text: getVisibleText(el).slice(0, textMax),\n attrs: getAttrs(el),\n visible: isVisible(el),\n compound: compoundOf(el) ?? void 0\n };\n });\n return out;\n }\n function semanticCandidates(findArgs) {\n const wantRole = findArgs.role ? findArgs.role.toLowerCase() : null;\n const wantName = findArgs.name ? findArgs.name.toLowerCase() : null;\n const wantText = findArgs.text ? findArgs.text.toLowerCase() : null;\n const all = Array.from(document.querySelectorAll("*"));\n return all.filter((el) => {\n if (!isVisible(el)) return false;\n if (wantRole) {\n const role = roleOf(el);\n if (!role || role.toLowerCase() !== wantRole) return false;\n }\n if (wantName && !accessibleName(el).toLowerCase().includes(wantName)) return false;\n if (wantText && !(el.textContent || "").toLowerCase().includes(wantText)) return false;\n return true;\n });\n }\n function pageState() {\n return {\n url: location.href,\n title: document.title,\n width: window.innerWidth,\n height: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio\n };\n }\n function goto(gotoArgs) {\n if (!gotoArgs.url) return { error: "url_required" };\n let sameUrl = false;\n try {\n sameUrl = new URL(gotoArgs.url, window.location.href).href === window.location.href;\n } catch {\n sameUrl = false;\n }\n if (sameUrl) {\n if (gotoArgs.reload === true) {\n window.location.reload();\n return { navigated: true, reloaded: true, url: gotoArgs.url };\n }\n return { navigated: false, reused: true, url: gotoArgs.url };\n }\n window.location.href = gotoArgs.url;\n return { navigated: true, url: gotoArgs.url };\n }\n function evalScript(evalArgs) {\n if (!evalArgs.script) return { error: "script_required" };\n try {\n const fn = new Function(evalArgs.script);\n const result = fn();\n return { result };\n } catch (e) {\n return { error: String(e) };\n }\n }\n function fillElement(fArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n if (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable) {\n const isContentEditable = el.isContentEditable;\n if (isContentEditable) {\n el.innerText = fArgs.text ?? "";\n } else {\n input.value = fArgs.text ?? "";\n }\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n const actual = isContentEditable ? el.innerText : input.value;\n return actual === (fArgs.text ?? "") ? { filled: true, verified: true, text: fArgs.text, actual, target, matches_n, match_level } : err("verify_failed", "Input value diverged after fill (controlled component?).", { actual });\n }\n return err("invalid_type", "fill only supports input, textarea, and contenteditable.");\n }\n function typeElement(tArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n el.focus({ preventScroll: true });\n input.value = tArgs.text ?? "";\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n let autocomplete = false;\n try {\n autocomplete = !!document.querySelector("[role=\'listbox\'], [role=\'combobox\'] [role=\'listbox\'], datalist, .ant-select-dropdown:not([style*=\'hidden\']), [id^=\'listbox\']");\n } catch {\n }\n return { typed: true, text: tArgs.text, target, matches_n, match_level, autocomplete };\n }\n function clickElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.scrollIntoView({ block: "center" });\n el.click();\n return { clicked: true, target, matches_n, match_level, tag: el.tagName.toLowerCase(), text: getVisibleText(el) };\n }\n function selectElement(sArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n if (el.tagName !== "SELECT") {\n return err(ERROR_CODES.NOT_A_SELECT, "select was called on a non-<select> element.", { tag: el.tagName.toLowerCase() });\n }\n const select = el;\n const want = sArgs.label ?? sArgs.value ?? sArgs.option;\n if (!want) return err("option_required", "Provide label or value.");\n const byLabel = Array.from(select.options).find((o) => (o.textContent || "").trim() === want);\n const byValue = Array.from(select.options).find((o) => o.value === want);\n const opt = byLabel ?? byValue;\n if (!opt) {\n const available = Array.from(select.options).slice(0, 50).map((o) => (o.textContent || "").trim());\n return err(ERROR_CODES.OPTION_NOT_FOUND, "Option not found.", { available });\n }\n select.value = opt.value;\n select.dispatchEvent(new Event("input", { bubbles: true }));\n select.dispatchEvent(new Event("change", { bubbles: true }));\n return { selected: true, value: select.value, target, matches_n, match_level };\n }\n function pressKeys(kArgs) {\n const keyCombo = kArgs.keys ?? kArgs.key ?? "";\n if (!keyCombo) return { error: "keys_required" };\n const comboParts = keyCombo.split("+").map((s) => s.trim());\n const main = comboParts[comboParts.length - 1];\n const ctrl = comboParts.includes("Control") || comboParts.includes("Ctrl") || comboParts.includes("Meta") || comboParts.includes("Cmd");\n const alt = comboParts.includes("Alt");\n const shift = comboParts.includes("Shift");\n const target = document.activeElement ?? document.body;\n const opts = { key: main, bubbles: true, cancelable: true, ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: comboParts.includes("Meta") || comboParts.includes("Cmd") };\n target.dispatchEvent(new KeyboardEvent("keydown", opts));\n target.dispatchEvent(new KeyboardEvent("keypress", opts));\n target.dispatchEvent(new KeyboardEvent("keyup", opts));\n return { pressed: true, key: keyCombo };\n }\n function hoverElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const rect = el.getBoundingClientRect();\n const opts = { bubbles: true, cancelable: true, clientX: rect.x + rect.width / 2, clientY: rect.y + rect.height / 2 };\n el.dispatchEvent(new MouseEvent("mouseover", opts));\n el.dispatchEvent(new MouseEvent("mousemove", opts));\n return { hovered: true, target, matches_n, match_level };\n }\n function focusElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.focus({ preventScroll: true });\n return { focused: true, target, matches_n, match_level };\n }\n function dblclickElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, cancelable: true }));\n return { dblclicked: true, target, matches_n, match_level };\n }\n function setChecked(want) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n const isAria = input.type !== "checkbox" && input.type !== "radio" && el.getAttribute("aria-checked") != null;\n if (!isAria && input.type !== "checkbox" && input.type !== "radio") {\n return err("invalid_type", "Target is not a checkbox/radio/aria-checked control.", { tag: el.tagName.toLowerCase() });\n }\n if (isAria) {\n el.setAttribute("aria-checked", String(want));\n el.dispatchEvent(new Event("change", { bubbles: true }));\n return { [want ? "checked" : "unchecked"]: true, changed: true, target, matches_n, match_level, kind: "aria" };\n }\n if (input.type === "radio" && !want) {\n return err("invalid_type", "Radio buttons cannot be unchecked directly; select another radio in the group.");\n }\n const changed = input.checked !== want;\n input.checked = want;\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n return { [want ? "checked" : "unchecked"]: true, changed, target, matches_n, match_level, kind: input.type };\n }\n function scrollElement(sArgs) {\n const dir = sArgs.direction ?? "down";\n const amount = sArgs.amount ?? 500;\n if (dir !== "up" && dir !== "down") return { error: "invalid_direction" };\n const dy = dir === "down" ? amount : -amount;\n window.scrollBy({ top: dy, behavior: "instant" });\n return {\n scrolled: true,\n direction: dir,\n amount,\n scrollY: Math.round(window.scrollY),\n scrollHeight: document.documentElement.scrollHeight\n };\n }\n function backNav() {\n history.back();\n return { back: true };\n }\n function htmlJsonTree(el, depth, maxDepth, childrenMax, textMax, truncated) {\n const children = Array.from(el.children);\n const ownText = (el.textContent || "").replace(/\\s+/g, " ").trim();\n const node = {\n tag: el.tagName.toLowerCase(),\n attrs: getAttrs(el),\n text: ownText.length > textMax ? (ownText.slice(0, textMax), truncated.text_truncated = (truncated.text_truncated || 0) + 1, ownText.slice(0, textMax)) : ownText,\n children: [],\n compound: compoundOf(el) ?? void 0\n };\n if (depth < maxDepth) {\n let kids = children;\n if (children.length > childrenMax) {\n truncated.children_dropped = (truncated.children_dropped || 0) + (children.length - childrenMax);\n kids = children.slice(0, childrenMax);\n }\n node.children = kids.map((c) => htmlJsonTree(c, depth + 1, maxDepth, childrenMax, textMax, truncated));\n } else if (children.length) {\n truncated.depth = (truncated.depth || 0) + 1;\n }\n return node;\n }\n function getElement(gArgs) {\n const what = gArgs.what ?? "text";\n if (what === "title") return { value: document.title, matches_n: 1, match_level: "exact" };\n if (what === "url") return { value: location.href, matches_n: 1, match_level: "exact" };\n if (what === "html") {\n if (gArgs["as"] === "json") {\n const root2 = gArgs.css ? document.querySelector(gArgs.css) : document.body;\n if (!root2) return err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: gArgs.css });\n const truncated = {};\n const tree = htmlJsonTree(root2, 0, gArgs.depth ?? 6, gArgs["children-max"] ?? 20, gArgs["text-max"] ?? 200, truncated);\n return {\n value: tree,\n truncated: Object.keys(truncated).length ? truncated : void 0\n };\n }\n const root = gArgs.css ? document.querySelector(gArgs.css) : document.body;\n if (!root) return err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: gArgs.css });\n let html = root.outerHTML;\n if (gArgs.max && html.length > gArgs.max) html = html.slice(0, gArgs.max) + "\\u2026";\n return { value: html };\n }\n const r = resolveTargetRich(gArgs);\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level } = r;\n switch (what) {\n case "text": {\n const t = getVisibleText(el);\n return { value: t.length > 2e3 ? t.slice(0, 2e3) : t, matches_n, match_level };\n }\n case "value":\n return { value: el.value ?? el.innerText ?? null, matches_n, match_level };\n case "attributes":\n return { value: getAttrs(el), matches_n, match_level };\n default:\n return { error: `unknown_what: ${what}` };\n }\n }\n function extractMarkdown(eArgs) {\n const chunkSize = eArgs["chunk-size"] ?? 0;\n const start = eArgs.start ?? 0;\n const scope = eArgs.selector ? document.querySelector(eArgs.selector) : document.querySelector("main") || document.querySelector("article") || document.body;\n if (!scope) return { error: "scope_not_found" };\n const parts = [];\n const walker = document.createTreeWalker(scope, NodeFilter.SHOW_ELEMENT);\n let node;\n while (node = walker.nextNode()) {\n const el = node;\n const tag = el.tagName.toLowerCase();\n const text = getVisibleText(el);\n if (!text) continue;\n if (tag === "h1") parts.push(`# ${text}`);\n else if (tag === "h2") parts.push(`## ${text}`);\n else if (tag === "h3") parts.push(`### ${text}`);\n else if (tag === "p" || tag === "li") parts.push(text);\n else if (tag === "a") parts.push(`[${text}](${el.getAttribute("href") ?? ""})`);\n }\n const full = parts.join("\\n\\n");\n const sliced = chunkSize > 0 ? full.slice(start, start + chunkSize) : full.slice(start);\n return {\n url: location.href,\n title: document.title,\n selector: eArgs.selector ?? "auto",\n total_chars: full.length,\n chunk_size: chunkSize,\n start,\n end: start + sliced.length,\n next_start_char: start + sliced.length < full.length ? start + sliced.length : null,\n content: sliced\n };\n }\n function listFrames() {\n const frames = Array.from(document.querySelectorAll("iframe")).map((f, index) => {\n const src = f.getAttribute("src") ?? "";\n let crossOrigin = false;\n try {\n if (src && /^https?:/.test(src)) crossOrigin = new URL(src, location.href).origin !== location.origin;\n } catch {\n }\n return { index, url: src, name: f.getAttribute("name") ?? "", cross_origin: crossOrigin };\n });\n return { frames };\n }\n function screenshotMeta() {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n url: location.href,\n title: document.title\n };\n }\n function startTitleMarquee(tArgs) {\n const g = globalThis;\n if (g.__cpMarquee) {\n clearInterval(g.__cpMarquee.interval);\n } else {\n g.__cpMarquee = { original: document.title, interval: 0 };\n }\n const prefix = tArgs.text ?? "\\u{1F916}";\n const base = g.__cpMarquee.original;\n const full = `${prefix} ${base}`;\n const speed = Math.max(100, Math.min(2e3, tArgs.speed ?? 300));\n let pos = 0;\n const tick = () => {\n const display = full.slice(pos) + " " + full.slice(0, pos);\n document.title = display.slice(0, 60);\n pos = (pos + 1) % (full.length + 1 || 1);\n };\n tick();\n g.__cpMarquee.interval = window.setInterval(tick, speed);\n return { started: true, original: base, prefix, speed };\n }\n function stopTitleMarquee() {\n const g = globalThis;\n const state = g.__cpMarquee;\n if (!state) return { restored: false, reason: "no_active_marquee" };\n clearInterval(state.interval);\n document.title = state.original;\n delete g.__cpMarquee;\n return { restored: true, original: state.original };\n }\n function networkEntriesLegacy(nArgs) {\n const all = performance.getEntriesByType("resource").map((e) => ({\n url: e.name,\n type: e.initiatorType ?? "unknown",\n duration: Math.round(e.duration)\n }));\n let entries = all;\n if (nArgs.filter) {\n const keywords = nArgs.filter.split(",").map((s) => s.trim().toLowerCase());\n entries = all.filter((e) => keywords.some((kw) => e.url.toLowerCase().includes(kw)));\n }\n return { entries, count: entries.length, source: "performance-api" };\n }\n async function waitForCondition(wArgs) {\n const timeout = wArgs.timeout ?? 1e4;\n if (wArgs.time != null) {\n await new Promise((res) => setTimeout(res, Math.min(wArgs.time * 1e3, 3e4)));\n return { waited: true, kind: "time", seconds: wArgs.time };\n }\n const deadline = Date.now() + timeout;\n const pollInterval = 200;\n if (wArgs.selector) {\n while (Date.now() < deadline) {\n if (document.querySelector(wArgs.selector)) return { waited: true, kind: "selector", selector: wArgs.selector, elapsedMs: timeout - (deadline - Date.now()) };\n await new Promise((res) => setTimeout(res, pollInterval));\n }\n return err("wait_timeout", `Selector not found within ${timeout}ms.`, { selector: wArgs.selector });\n }\n if (wArgs.text) {\n const lower = wArgs.text.toLowerCase();\n while (Date.now() < deadline) {\n if ((document.body.textContent || "").toLowerCase().includes(lower)) {\n return { waited: true, kind: "text", text: wArgs.text, elapsedMs: timeout - (deadline - Date.now()) };\n }\n await new Promise((res) => setTimeout(res, pollInterval));\n }\n return err("wait_timeout", `Text not found within ${timeout}ms.`, { text: wArgs.text });\n }\n return { error: "no_condition" };\n }\n function uploadFiles(uArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n if (input.type !== "file") return err("invalid_type", "Target is not an input[type=file].", { tag: el.tagName.toLowerCase() });\n try {\n const dt = new DataTransfer();\n for (const name of uArgs.files ?? []) {\n dt.items.add(new File([""], name.split("/").pop() || name));\n }\n input.files = dt.files;\n } catch {\n }\n input.dispatchEvent(new Event("change", { bubbles: true }));\n return {\n uploaded: true,\n files: uArgs.files ?? [],\n file_names: (uArgs.files ?? []).map((f) => f.split("/").pop()),\n target,\n matches_n,\n match_level,\n multiple: input.multiple,\n accept: input.accept || null\n };\n }\n function dragElements(dArgs) {\n const r1 = resolveTargetRich(sel());\n if ("errorEnvelope" in r1) return r1.errorEnvelope;\n const r2 = resolveTargetRich({ ref: dArgs.ref2, css: dArgs.css2 });\n if ("errorEnvelope" in r2) return r2.errorEnvelope;\n const src = r1.el;\n const tgt = r2.el;\n const make = (type) => {\n const r1rect = src.getBoundingClientRect();\n return new DragEvent(type, { bubbles: true, cancelable: true, clientX: r1rect.x + r1rect.width / 2, clientY: r1rect.y + r1rect.height / 2 });\n };\n src.dispatchEvent(make("dragstart"));\n tgt.dispatchEvent(new DragEvent("dragenter", { bubbles: true }));\n tgt.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true }));\n tgt.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true }));\n src.dispatchEvent(new DragEvent("dragend", { bubbles: true }));\n return { dragged: true, source: String(sel().ref ?? sel().css ?? ""), target: String(dArgs.ref2 ?? dArgs.css2 ?? ""), source_matches_n: r1.matches_n, target_matches_n: r2.matches_n };\n }\n switch (command) {\n case "cp.state":\n return a.tree === false ? pageState() : buildStateTree();\n case "cp.snapshot":\n return buildStateTree();\n case "cp.find":\n return findElements({\n css: a.css,\n text: a.text,\n limit: a.limit,\n role: a.role,\n name: a.name,\n label: a.label,\n testid: a.testid,\n "text-max": a["text-max"]\n });\n case "cp.click":\n return clickElement();\n case "cp.type":\n return typeElement({ text: a.text });\n case "cp.goto":\n return goto({ url: a.url });\n case "cp.eval":\n return evalScript({ script: a.script });\n case "cp.fill":\n return fillElement({ text: a.text });\n case "cp.select":\n return selectElement({ label: a.label, value: a.value, option: a.option });\n case "cp.keys":\n return pressKeys({ keys: a.keys, key: a.key });\n case "cp.hover":\n return hoverElement();\n case "cp.focus":\n return focusElement();\n case "cp.dblclick":\n return dblclickElement();\n case "cp.check":\n return setChecked(true);\n case "cp.uncheck":\n return setChecked(false);\n case "cp.scroll":\n return scrollElement({ direction: a.direction, amount: a.amount });\n case "cp.back":\n return backNav();\n case "cp.get":\n return getElement({\n what: a.what,\n css: a.css,\n ref: a.ref,\n nth: a.nth,\n "as": a["as"],\n depth: a.depth,\n "children-max": a["children-max"],\n "text-max": a["text-max"],\n max: a.max,\n role: a.role,\n name: a.name,\n label: a.label,\n text: a.text,\n testid: a.testid\n });\n case "cp.extract":\n return extractMarkdown({\n "chunk-size": a["chunk-size"],\n start: a.start,\n selector: a.selector\n });\n case "cp.frames":\n return listFrames();\n case "cp.screenshot":\n return screenshotMeta();\n case "cp.network":\n return networkEntriesLegacy({ filter: a.filter });\n case "cp.wait":\n return waitForCondition({\n selector: a.selector,\n text: a.text,\n time: a.time,\n timeout: a.timeout\n });\n case "cp.upload":\n return uploadFiles({ files: a.files });\n case "cp.drag":\n return dragElements({ ref2: a.ref2, css2: a.css2 });\n case "cp.title.marquee":\n return startTitleMarquee({ text: a.text, speed: a.speed });\n case "cp.title.restore":\n return stopTitleMarquee();\n default:\n return { error: `unknown_command: ${command}` };\n }\n }\n var RUNTIME_VERSION = "0.3.0-opencli-parity";\n globalThis.__cpRunControl = runControl;\n globalThis.__cpRuntimeVersion = RUNTIME_VERSION;\n})();\n';
|
|
1345
|
+
var control_runtime_source_default = '"use strict";\n(() => {\n // src/shared/control-runtime.ts\n function getRefStore() {\n const g = globalThis;\n if (!g.__cpRefs) {\n g.__cpRefs = { counter: 1, refs: /* @__PURE__ */ new Map(), cssPaths: /* @__PURE__ */ new Map() };\n }\n return g.__cpRefs;\n }\n function allocRef(el) {\n const store = getRefStore();\n for (const [ref2, existing] of store.refs) {\n if (existing === el) return ref2;\n }\n const ref = store.counter++;\n store.refs.set(ref, el);\n store.cssPaths.set(ref, uniqueCssPath(el));\n return ref;\n }\n function uniqueCssPath(el) {\n if (el.id) return `#${CSS.escape(el.id)}`;\n const parts = [];\n let cur = el;\n while (cur && cur !== document.body && cur !== document.documentElement) {\n let sel = cur.tagName.toLowerCase();\n if (cur.id) {\n parts.unshift(`#${CSS.escape(cur.id)}`);\n break;\n }\n const parent = cur.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);\n if (sameTag.length > 1) {\n const idx = sameTag.indexOf(cur) + 1;\n sel += `:nth-of-type(${idx})`;\n }\n }\n parts.unshift(sel);\n cur = parent;\n }\n return parts.join(" > ");\n }\n function err(code, message, extra) {\n return { error: { code, message, ...extra || {} } };\n }\n var ERROR_CODES = {\n NOT_FOUND: "not_found",\n STALE_REF: "stale_ref",\n INVALID_SELECTOR: "invalid_selector",\n SELECTOR_NOT_FOUND: "selector_not_found",\n SELECTOR_AMBIGUOUS: "selector_ambiguous",\n SELECTOR_NTH_OUT_OF_RANGE: "selector_nth_out_of_range",\n OPTION_NOT_FOUND: "option_not_found",\n NOT_A_SELECT: "not_a_select"\n };\n var IMPLICIT_ROLE = {\n a: "link",\n button: "button",\n select: "combobox",\n textarea: "textbox",\n h1: "heading",\n h2: "heading",\n h3: "heading",\n h4: "heading",\n h5: "heading",\n h6: "heading",\n img: "img",\n nav: "navigation",\n ul: "list",\n ol: "list",\n li: "listitem",\n main: "main",\n article: "article",\n aside: "complementary",\n header: "banner",\n footer: "contentinfo",\n form: "form",\n table: "table",\n summary: "button",\n option: "option",\n output: "status"\n };\n function roleOf(el) {\n const explicit = el.getAttribute("role");\n if (explicit) return explicit.split(/\\s+/)[0];\n const tag = el.tagName.toLowerCase();\n if (tag === "input") {\n const type = el.type || "text";\n if (type === "hidden") return null;\n if (type === "checkbox") return "checkbox";\n if (type === "radio") return "radio";\n if (type === "button" || type === "submit" || type === "reset") return "button";\n if (type === "range") return "slider";\n return "textbox";\n }\n if (tag === "progress") return "progressbar";\n if (tag === "meter") return "meter";\n return IMPLICIT_ROLE[tag] ?? null;\n }\n function accessibleName(el) {\n const aria = el.getAttribute("aria-label");\n if (aria) return aria.trim();\n const labelledby = el.getAttribute("aria-labelledby");\n if (labelledby) {\n const parts = labelledby.split(/\\s+/).map((id2) => document.getElementById(id2)?.textContent?.trim() || "");\n const joined = parts.filter(Boolean).join(" ");\n if (joined) return joined.slice(0, 120);\n }\n const id = el.getAttribute("id");\n if (id) {\n const label = document.querySelector(`label[for="${CSS.escape(id)}"]`);\n if (label?.textContent?.trim()) return label.textContent.trim().slice(0, 120);\n }\n if (el.tagName.toLowerCase() === "input") {\n const type = el.type;\n if (type === "button" || type === "submit" || type === "reset") {\n const v = el.value;\n if (v) return v;\n }\n }\n const alt = el.getAttribute("alt");\n if (alt) return alt;\n const title = el.getAttribute("title");\n if (title) return title;\n const placeholder = el.getAttribute("placeholder");\n if (placeholder) return placeholder;\n return (el.textContent || "").replace(/\\s+/g, " ").trim().slice(0, 120);\n }\n function isVisible(el) {\n if (!(el instanceof HTMLElement) && !(el instanceof SVGElement)) return false;\n const html = el;\n if (html.hasAttribute("hidden")) return false;\n if (html.closest("[hidden]")) return false;\n const style = window.getComputedStyle(html);\n if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;\n if (el instanceof HTMLElement && el.offsetParent === null) {\n const pos = style.position;\n if (pos !== "fixed" && pos !== "sticky") return false;\n }\n return true;\n }\n var INTERACTIVE_SELECTOR = "a[href], button, input, textarea, select, summary, [role], [contenteditable=\'\'], [contenteditable=\'true\'], [onclick], [tabindex]";\n function isInteractive(el) {\n return el.matches(INTERACTIVE_SELECTOR);\n }\n function testidSelector(testid) {\n return `[data-testid="${CSS.escape(testid)}"], [data-test-id="${CSS.escape(testid)}"], [data-test="${CSS.escape(testid)}"], [data-cy="${CSS.escape(testid)}"]`;\n }\n function resolveTargetRich(sel) {\n const targetDesc = sel.ref != null ? String(sel.ref) : sel.css ?? (sel.role || sel.name || sel.label || sel.testid || sel.text ? `${sel.role ? `role=${sel.role} ` : ""}${sel.name ? `name=${sel.name} ` : ""}${sel.label ? `label=${sel.label} ` : ""}${sel.testid ? `testid=${sel.testid} ` : ""}${sel.text ? `text=${sel.text}` : ""}`.trim() : "");\n if (sel.ref != null) {\n const store = getRefStore();\n const el = store.refs.get(sel.ref);\n if (el && document.contains(el) && isVisible(el)) {\n return { el, matches_n: 1, match_level: "exact", target: targetDesc };\n }\n if (el && !document.contains(el)) {\n const cssPath = store.cssPaths.get(sel.ref);\n if (cssPath) {\n const re = document.querySelector(cssPath);\n if (re && isVisible(re)) {\n store.refs.set(sel.ref, re);\n return { el: re, matches_n: 1, match_level: "reidentified", target: targetDesc };\n }\n }\n return { errorEnvelope: err(ERROR_CODES.STALE_REF, "Numeric ref is no longer in the DOM. Re-run state.") };\n }\n return { errorEnvelope: err(ERROR_CODES.NOT_FOUND, "Numeric ref is no longer in the DOM. Re-run state.") };\n }\n let candidates = [];\n if (sel.css) {\n try {\n candidates = Array.from(document.querySelectorAll(sel.css));\n } catch {\n return { errorEnvelope: err(ERROR_CODES.INVALID_SELECTOR, "CSS was rejected by querySelectorAll.", { selector: sel.css }) };\n }\n if (candidates.length === 0) {\n return { errorEnvelope: err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: sel.css, candidates: suggestCandidates() }) };\n }\n } else if (sel.testid) {\n try {\n candidates = Array.from(document.querySelectorAll(testidSelector(sel.testid)));\n } catch {\n return { errorEnvelope: err(ERROR_CODES.INVALID_SELECTOR, "Invalid testid.", { testid: sel.testid }) };\n }\n } else if (sel.role || sel.name || sel.label || sel.text) {\n const wantRole = sel.role ? sel.role.toLowerCase() : null;\n const wantName = sel.name ? sel.name.toLowerCase() : null;\n const wantText = sel.text ? sel.text.toLowerCase() : null;\n let labelTargets = [];\n if (sel.label) {\n const lower = sel.label.toLowerCase();\n labelTargets = Array.from(document.querySelectorAll("label")).filter((l) => (l.textContent || "").trim().toLowerCase().includes(lower)).map((l) => {\n const forId = l.getAttribute("for");\n if (forId) return document.getElementById(forId);\n return l.querySelector("input, select, textarea");\n }).filter((x) => !!x);\n }\n const all = Array.from(document.querySelectorAll("*"));\n candidates = all.filter((el) => {\n if (!isVisible(el)) return false;\n const role = roleOf(el);\n if (wantRole && (!role || role.toLowerCase() !== wantRole)) return false;\n if (wantName) {\n const name = accessibleName(el).toLowerCase();\n if (!name.includes(wantName)) return false;\n }\n if (wantText) {\n const t = (el.textContent || "").replace(/\\s+/g, " ").trim().toLowerCase();\n if (!t.includes(wantText)) return false;\n }\n return true;\n });\n if (labelTargets.length) {\n const set = new Set(labelTargets);\n candidates = labelTargets.concat(candidates.filter((c) => !set.has(c)));\n }\n if (candidates.length === 0) {\n return {\n errorEnvelope: err(\n ERROR_CODES.SELECTOR_NOT_FOUND,\n "Semantic locator matches 0 elements.",\n { role: sel.role, name: sel.name, label: sel.label, text: sel.text, candidates: suggestCandidates() }\n )\n };\n }\n } else {\n return { errorEnvelope: err("no_target", "Provide a numeric ref, CSS selector, or semantic locator (--role/--name/--label/--text/--testid).") };\n }\n const visible = candidates.filter(isVisible);\n const pool = visible.length ? visible : candidates;\n if (sel.nth != null) {\n if (sel.nth < 0 || sel.nth >= pool.length) {\n return { errorEnvelope: err(ERROR_CODES.SELECTOR_NTH_OUT_OF_RANGE, `--nth ${sel.nth} beyond match count ${pool.length}.`) };\n }\n const el = pool[sel.nth];\n return { el, matches_n: pool.length, match_level: "reidentified", target: targetDesc };\n }\n if (pool.length > 1) {\n return {\n errorEnvelope: err(ERROR_CODES.SELECTOR_AMBIGUOUS, `CSS matches ${pool.length} elements and no --nth given.`, {\n matches_n: pool.length,\n candidates: pool.slice(0, 5).map((el) => uniqueCssPath(el))\n })\n };\n }\n return { el: pool[0], matches_n: 1, match_level: "reidentified", target: targetDesc };\n }\n function suggestCandidates() {\n const store = getRefStore();\n const recent = Array.from(store.refs.entries()).slice(-5).map(([, el]) => uniqueCssPath(el));\n return recent;\n }\n function compoundOf(el) {\n const tag = el.tagName.toLowerCase();\n if (tag === "input") {\n const input = el;\n const type = input.type;\n if (["date", "time", "datetime-local", "month", "week"].includes(type)) {\n const formatMap = {\n date: "YYYY-MM-DD",\n time: "HH:mm",\n "datetime-local": "YYYY-MM-DDTHH:mm",\n month: "YYYY-MM",\n week: "YYYY-Www"\n };\n return {\n control: type,\n format: formatMap[type] ?? type,\n current: input.value || "",\n min: input.min || null,\n max: input.max || null\n };\n }\n if (type === "file") {\n const current = Array.from(input.files ?? []).map((f) => f.name);\n return { control: "file", multiple: input.multiple, current, accept: input.accept || null };\n }\n return null;\n }\n if (tag === "select") {\n const select = el;\n const options = Array.from(select.options).slice(0, 50).map((o) => ({\n label: (o.textContent || "").trim(),\n value: o.value,\n selected: o.selected\n }));\n return {\n control: "select",\n multiple: select.multiple,\n current: select.selectedOptions[0] ? (select.selectedOptions[0].textContent || "").trim() : "",\n options,\n options_total: select.options.length\n };\n }\n return null;\n }\n var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "meta", "link", "head", "base", "template", "path", "circle", "rect", "g", "defs", "polygon", "line"]);\n function describeElement(el, ref) {\n const tag = el.tagName.toLowerCase();\n const role = el.getAttribute("role") ?? null;\n const name = accessibleName(el);\n const parts = [];\n if (ref != null) parts.push(`[${ref}]`);\n parts.push(tag);\n if (role && role !== IMPLICIT_ROLE[tag]) parts.push(`role=${role}`);\n if (tag === "input") {\n const input = el;\n if (input.type && input.type !== "text") parts.push(`[${input.type}]`);\n if (input.disabled) parts.push("(disabled)");\n }\n if (name) parts.push(JSON.stringify(name.length > 60 ? name.slice(0, 57) + "\\u2026" : name));\n if (tag === "a") {\n const href = el.getAttribute("href");\n if (href) parts.push(`\\u2192 ${href.slice(0, 50)}`);\n }\n if (el.disabled) parts.push("(disabled)");\n return parts.join(" ");\n }\n function buildStateTree() {\n const lines = [];\n const compounds = {};\n const budget = { nodes: 0, chars: 0, truncated: false };\n const MAX_NODES = 400;\n const MAX_CHARS = 9e3;\n let hiddenInteractive = 0;\n function emit(depth, text) {\n const line = " ".repeat(Math.min(depth, 12)) + text;\n lines.push(line);\n budget.chars += line.length;\n }\n function walk(el, depth) {\n if (budget.nodes >= MAX_NODES || budget.chars >= MAX_CHARS) {\n budget.truncated = true;\n return;\n }\n const tag = el.tagName.toLowerCase();\n if (SKIP_TAGS.has(tag)) return;\n budget.nodes++;\n const visible = isVisible(el);\n const interactive = isInteractive(el);\n if (interactive) {\n if (visible) {\n const ref = allocRef(el);\n emit(depth, describeElement(el, ref));\n const compound = compoundOf(el);\n if (compound) compounds[String(ref)] = compound;\n } else {\n hiddenInteractive++;\n return;\n }\n } else if (visible) {\n if (/^h[1-6]$/.test(tag)) {\n const t = accessibleName(el);\n if (t) emit(depth, `${"#".repeat(Number(tag[1]))} ${t}`);\n } else if (tag === "p" || tag === "li" || tag === "td" || tag === "th" || tag === "figcaption" || tag === "blockquote") {\n const ownText = ownTextOf(el);\n if (ownText) emit(depth, ownText.slice(0, 100));\n } else if (tag === "img") {\n const alt = el.getAttribute("alt") || "";\n if (alt) emit(depth, `img ${JSON.stringify(alt.slice(0, 60))}`);\n }\n }\n if (depth < 14) {\n for (const child of Array.from(el.children)) {\n walk(child, depth + 1);\n if (budget.truncated) return;\n }\n }\n }\n function ownTextOf(el) {\n let text = "";\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) text += node.textContent || "";\n }\n return text.replace(/\\s+/g, " ").trim();\n }\n const root = document.querySelector("main") || document.querySelector("#root") || document.body;\n emit(0, `# ${document.title}`);\n walk(root, 0);\n const scrollable = document.documentElement.scrollHeight > window.innerHeight + 200;\n if (scrollable) {\n emit(0, "\\u2195 page is scrollable \\u2014 run cp.scroll down to reveal more");\n }\n if (hiddenInteractive > 0) {\n emit(0, `\\u2139 ${hiddenInteractive} hidden interactive element(s) not tagged`);\n }\n if (budget.truncated) {\n emit(0, "\\u2026 (snapshot truncated by budget \\u2014 narrow with a selector or scroll)");\n }\n return {\n url: location.href,\n title: document.title,\n text: lines.join("\\n"),\n compounds,\n hints: { scroll: scrollable, hidden_interactive: hiddenInteractive },\n truncated: budget.truncated\n };\n }\n function runControl(command, args) {\n const a = args || {};\n function getVisibleText(el) {\n return (el.textContent || "").trim().replace(/\\s+/g, " ").slice(0, 120);\n }\n function getAttrs(el) {\n const attrs = {};\n for (const name of ["id", "class", "name", "type", "href", "src", "placeholder", "value", "role", "aria-label", "data-testid"]) {\n attrs[name] = el.getAttribute(name);\n }\n return attrs;\n }\n function sel() {\n return {\n ref: a.ref,\n css: a.css,\n nth: a.nth,\n role: a.role,\n name: a.name,\n label: a.label,\n text: a.text,\n testid: a.testid\n };\n }\n function findElements(findArgs) {\n const limit = findArgs.limit ?? 20;\n const textMax = findArgs["text-max"] ?? 120;\n let elements = [];\n if (findArgs.css) {\n try {\n elements = Array.from(document.querySelectorAll(findArgs.css));\n } catch {\n return err(ERROR_CODES.INVALID_SELECTOR, "CSS was rejected by querySelectorAll.", { selector: findArgs.css });\n }\n } else if (findArgs.testid) {\n try {\n elements = Array.from(document.querySelectorAll(testidSelector(findArgs.testid)));\n } catch {\n return err(ERROR_CODES.INVALID_SELECTOR, "Invalid testid.");\n }\n } else if (findArgs.role || findArgs.name || findArgs.label) {\n const r = resolveTargetRich({ role: findArgs.role, name: findArgs.name, label: findArgs.label, text: findArgs.text });\n if ("errorEnvelope" in r) return r.errorEnvelope;\n elements = document.querySelectorAll("*").length ? semanticCandidates(findArgs) : [];\n } else if (findArgs.text) {\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);\n const lower = findArgs.text.toLowerCase();\n const seen = /* @__PURE__ */ new Set();\n let node;\n while (node = walker.nextNode()) {\n if (node.textContent?.toLowerCase().includes(lower)) {\n const el = node.parentElement;\n if (el && !seen.has(el)) {\n seen.add(el);\n elements.push(el);\n if (elements.length >= limit) break;\n }\n }\n }\n } else {\n elements = Array.from(document.querySelectorAll(INTERACTIVE_SELECTOR));\n }\n const out = elements.slice(0, limit).map((el, i) => {\n const ref = allocRef(el);\n return {\n nth: i,\n ref,\n tag: el.tagName.toLowerCase(),\n role: roleOf(el),\n text: getVisibleText(el).slice(0, textMax),\n attrs: getAttrs(el),\n visible: isVisible(el),\n compound: compoundOf(el) ?? void 0\n };\n });\n return out;\n }\n function semanticCandidates(findArgs) {\n const wantRole = findArgs.role ? findArgs.role.toLowerCase() : null;\n const wantName = findArgs.name ? findArgs.name.toLowerCase() : null;\n const wantText = findArgs.text ? findArgs.text.toLowerCase() : null;\n const all = Array.from(document.querySelectorAll("*"));\n return all.filter((el) => {\n if (!isVisible(el)) return false;\n if (wantRole) {\n const role = roleOf(el);\n if (!role || role.toLowerCase() !== wantRole) return false;\n }\n if (wantName && !accessibleName(el).toLowerCase().includes(wantName)) return false;\n if (wantText && !(el.textContent || "").toLowerCase().includes(wantText)) return false;\n return true;\n });\n }\n function pageState() {\n return {\n url: location.href,\n title: document.title,\n width: window.innerWidth,\n height: window.innerHeight,\n devicePixelRatio: window.devicePixelRatio\n };\n }\n function goto(gotoArgs) {\n if (!gotoArgs.url) return { error: "url_required" };\n let sameUrl = false;\n try {\n sameUrl = new URL(gotoArgs.url, window.location.href).href === window.location.href;\n } catch {\n sameUrl = false;\n }\n if (sameUrl) {\n if (gotoArgs.reload === true) {\n window.location.reload();\n return { navigated: true, reloaded: true, url: gotoArgs.url };\n }\n return { navigated: false, reused: true, url: gotoArgs.url };\n }\n window.location.href = gotoArgs.url;\n return { navigated: true, url: gotoArgs.url };\n }\n function evalScript(evalArgs) {\n if (!evalArgs.script) return { error: "script_required" };\n try {\n const fn = new Function(evalArgs.script);\n const result = fn();\n return { result };\n } catch (e) {\n return { error: String(e) };\n }\n }\n function fillElement(fArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n if (el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable) {\n const isContentEditable = el.isContentEditable;\n if (isContentEditable) {\n el.innerText = fArgs.text ?? "";\n } else {\n input.value = fArgs.text ?? "";\n }\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n const actual = isContentEditable ? el.innerText : input.value;\n return actual === (fArgs.text ?? "") ? { filled: true, verified: true, text: fArgs.text, actual, target, matches_n, match_level } : err("verify_failed", "Input value diverged after fill (controlled component?).", { actual });\n }\n return err("invalid_type", "fill only supports input, textarea, and contenteditable.");\n }\n function typeElement(tArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n el.focus({ preventScroll: true });\n input.value = tArgs.text ?? "";\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n let autocomplete = false;\n try {\n autocomplete = !!document.querySelector("[role=\'listbox\'], [role=\'combobox\'] [role=\'listbox\'], datalist, .ant-select-dropdown:not([style*=\'hidden\']), [id^=\'listbox\']");\n } catch {\n }\n return { typed: true, text: tArgs.text, target, matches_n, match_level, autocomplete };\n }\n function clickElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.scrollIntoView({ block: "center" });\n if (a.full === true) {\n const rect = el.getBoundingClientRect();\n const opts = {\n bubbles: true,\n cancelable: true,\n view: window,\n clientX: rect.x + rect.width / 2,\n clientY: rect.y + rect.height / 2,\n button: 0,\n buttons: 1,\n pointerId: 1,\n pointerType: "mouse",\n isPrimary: true\n };\n el.dispatchEvent(new PointerEvent("pointerdown", opts));\n el.dispatchEvent(new MouseEvent("mousedown", opts));\n el.dispatchEvent(new PointerEvent("pointerup", { ...opts, buttons: 0 }));\n el.dispatchEvent(new MouseEvent("mouseup", { ...opts, buttons: 0 }));\n el.dispatchEvent(new MouseEvent("click", { ...opts, buttons: 0 }));\n return { clicked: true, full: true, target, matches_n, match_level, tag: el.tagName.toLowerCase(), text: getVisibleText(el) };\n }\n el.click();\n return { clicked: true, target, matches_n, match_level, tag: el.tagName.toLowerCase(), text: getVisibleText(el) };\n }\n function selectElement(sArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n if (el.tagName !== "SELECT") {\n return err(ERROR_CODES.NOT_A_SELECT, "select was called on a non-<select> element.", { tag: el.tagName.toLowerCase() });\n }\n const select = el;\n const want = sArgs.label ?? sArgs.value ?? sArgs.option;\n if (!want) return err("option_required", "Provide label or value.");\n const byLabel = Array.from(select.options).find((o) => (o.textContent || "").trim() === want);\n const byValue = Array.from(select.options).find((o) => o.value === want);\n const opt = byLabel ?? byValue;\n if (!opt) {\n const available = Array.from(select.options).slice(0, 50).map((o) => (o.textContent || "").trim());\n return err(ERROR_CODES.OPTION_NOT_FOUND, "Option not found.", { available });\n }\n select.value = opt.value;\n select.dispatchEvent(new Event("input", { bubbles: true }));\n select.dispatchEvent(new Event("change", { bubbles: true }));\n return { selected: true, value: select.value, target, matches_n, match_level };\n }\n function pressKeys(kArgs) {\n const keyCombo = kArgs.keys ?? kArgs.key ?? "";\n if (!keyCombo) return { error: "keys_required" };\n const comboParts = keyCombo.split("+").map((s) => s.trim());\n const main = comboParts[comboParts.length - 1];\n const ctrl = comboParts.includes("Control") || comboParts.includes("Ctrl") || comboParts.includes("Meta") || comboParts.includes("Cmd");\n const alt = comboParts.includes("Alt");\n const shift = comboParts.includes("Shift");\n const target = document.activeElement ?? document.body;\n const opts = { key: main, bubbles: true, cancelable: true, ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: comboParts.includes("Meta") || comboParts.includes("Cmd") };\n target.dispatchEvent(new KeyboardEvent("keydown", opts));\n target.dispatchEvent(new KeyboardEvent("keypress", opts));\n target.dispatchEvent(new KeyboardEvent("keyup", opts));\n return { pressed: true, key: keyCombo };\n }\n function hoverElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const rect = el.getBoundingClientRect();\n const opts = { bubbles: true, cancelable: true, clientX: rect.x + rect.width / 2, clientY: rect.y + rect.height / 2 };\n el.dispatchEvent(new MouseEvent("mouseover", opts));\n el.dispatchEvent(new MouseEvent("mousemove", opts));\n return { hovered: true, target, matches_n, match_level };\n }\n function focusElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.focus({ preventScroll: true });\n return { focused: true, target, matches_n, match_level };\n }\n function dblclickElement() {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n el.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, cancelable: true }));\n return { dblclicked: true, target, matches_n, match_level };\n }\n function setChecked(want) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n const isAria = input.type !== "checkbox" && input.type !== "radio" && el.getAttribute("aria-checked") != null;\n if (!isAria && input.type !== "checkbox" && input.type !== "radio") {\n return err("invalid_type", "Target is not a checkbox/radio/aria-checked control.", { tag: el.tagName.toLowerCase() });\n }\n if (isAria) {\n el.setAttribute("aria-checked", String(want));\n el.dispatchEvent(new Event("change", { bubbles: true }));\n return { [want ? "checked" : "unchecked"]: true, changed: true, target, matches_n, match_level, kind: "aria" };\n }\n if (input.type === "radio" && !want) {\n return err("invalid_type", "Radio buttons cannot be unchecked directly; select another radio in the group.");\n }\n const changed = input.checked !== want;\n input.checked = want;\n input.dispatchEvent(new Event("input", { bubbles: true }));\n input.dispatchEvent(new Event("change", { bubbles: true }));\n return { [want ? "checked" : "unchecked"]: true, changed, target, matches_n, match_level, kind: input.type };\n }\n function scrollElement(sArgs) {\n const dir = sArgs.direction ?? "down";\n const amount = sArgs.amount ?? 500;\n if (dir !== "up" && dir !== "down") return { error: "invalid_direction" };\n const dy = dir === "down" ? amount : -amount;\n window.scrollBy({ top: dy, behavior: "instant" });\n return {\n scrolled: true,\n direction: dir,\n amount,\n scrollY: Math.round(window.scrollY),\n scrollHeight: document.documentElement.scrollHeight\n };\n }\n function backNav() {\n history.back();\n return { back: true };\n }\n function htmlJsonTree(el, depth, maxDepth, childrenMax, textMax, truncated) {\n const children = Array.from(el.children);\n const ownText = (el.textContent || "").replace(/\\s+/g, " ").trim();\n const node = {\n tag: el.tagName.toLowerCase(),\n attrs: getAttrs(el),\n text: ownText.length > textMax ? (ownText.slice(0, textMax), truncated.text_truncated = (truncated.text_truncated || 0) + 1, ownText.slice(0, textMax)) : ownText,\n children: [],\n compound: compoundOf(el) ?? void 0\n };\n if (depth < maxDepth) {\n let kids = children;\n if (children.length > childrenMax) {\n truncated.children_dropped = (truncated.children_dropped || 0) + (children.length - childrenMax);\n kids = children.slice(0, childrenMax);\n }\n node.children = kids.map((c) => htmlJsonTree(c, depth + 1, maxDepth, childrenMax, textMax, truncated));\n } else if (children.length) {\n truncated.depth = (truncated.depth || 0) + 1;\n }\n return node;\n }\n function getElement(gArgs) {\n const what = gArgs.what ?? "text";\n if (what === "title") return { value: document.title, matches_n: 1, match_level: "exact" };\n if (what === "url") return { value: location.href, matches_n: 1, match_level: "exact" };\n if (what === "html") {\n if (gArgs["as"] === "json") {\n const root2 = gArgs.css ? document.querySelector(gArgs.css) : document.body;\n if (!root2) return err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: gArgs.css });\n const truncated = {};\n const tree = htmlJsonTree(root2, 0, gArgs.depth ?? 6, gArgs["children-max"] ?? 20, gArgs["text-max"] ?? 200, truncated);\n return {\n value: tree,\n truncated: Object.keys(truncated).length ? truncated : void 0\n };\n }\n const root = gArgs.css ? document.querySelector(gArgs.css) : document.body;\n if (!root) return err(ERROR_CODES.SELECTOR_NOT_FOUND, "CSS matches 0 elements.", { selector: gArgs.css });\n let html = root.outerHTML;\n if (gArgs.max && html.length > gArgs.max) html = html.slice(0, gArgs.max) + "\\u2026";\n return { value: html };\n }\n const r = resolveTargetRich(gArgs);\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level } = r;\n switch (what) {\n case "text": {\n const t = getVisibleText(el);\n return { value: t.length > 2e3 ? t.slice(0, 2e3) : t, matches_n, match_level };\n }\n case "value":\n return { value: el.value ?? el.innerText ?? null, matches_n, match_level };\n case "attributes":\n return { value: getAttrs(el), matches_n, match_level };\n default:\n return { error: `unknown_what: ${what}` };\n }\n }\n function extractMarkdown(eArgs) {\n const chunkSize = eArgs["chunk-size"] ?? 0;\n const start = eArgs.start ?? 0;\n const scope = eArgs.selector ? document.querySelector(eArgs.selector) : document.querySelector("main") || document.querySelector("article") || document.body;\n if (!scope) return { error: "scope_not_found" };\n const parts = [];\n const walker = document.createTreeWalker(scope, NodeFilter.SHOW_ELEMENT);\n let node;\n while (node = walker.nextNode()) {\n const el = node;\n const tag = el.tagName.toLowerCase();\n const text = getVisibleText(el);\n if (!text) continue;\n if (tag === "h1") parts.push(`# ${text}`);\n else if (tag === "h2") parts.push(`## ${text}`);\n else if (tag === "h3") parts.push(`### ${text}`);\n else if (tag === "p" || tag === "li") parts.push(text);\n else if (tag === "a") parts.push(`[${text}](${el.getAttribute("href") ?? ""})`);\n }\n const full = parts.join("\\n\\n");\n const sliced = chunkSize > 0 ? full.slice(start, start + chunkSize) : full.slice(start);\n return {\n url: location.href,\n title: document.title,\n selector: eArgs.selector ?? "auto",\n total_chars: full.length,\n chunk_size: chunkSize,\n start,\n end: start + sliced.length,\n next_start_char: start + sliced.length < full.length ? start + sliced.length : null,\n content: sliced\n };\n }\n function listFrames() {\n const frames = Array.from(document.querySelectorAll("iframe")).map((f, index) => {\n const src = f.getAttribute("src") ?? "";\n let crossOrigin = false;\n try {\n if (src && /^https?:/.test(src)) crossOrigin = new URL(src, location.href).origin !== location.origin;\n } catch {\n }\n return { index, url: src, name: f.getAttribute("name") ?? "", cross_origin: crossOrigin };\n });\n return { frames };\n }\n function screenshotMeta() {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n url: location.href,\n title: document.title\n };\n }\n function startTitleMarquee(tArgs) {\n const g = globalThis;\n if (g.__cpMarquee) {\n clearInterval(g.__cpMarquee.interval);\n } else {\n g.__cpMarquee = { original: document.title, interval: 0 };\n }\n const prefix = tArgs.text ?? "\\u{1F916}";\n const base = g.__cpMarquee.original;\n const full = `${prefix} ${base}`;\n const speed = Math.max(100, Math.min(2e3, tArgs.speed ?? 300));\n let pos = 0;\n const tick = () => {\n const display = full.slice(pos) + " " + full.slice(0, pos);\n document.title = display.slice(0, 60);\n pos = (pos + 1) % (full.length + 1 || 1);\n };\n tick();\n g.__cpMarquee.interval = window.setInterval(tick, speed);\n return { started: true, original: base, prefix, speed };\n }\n function stopTitleMarquee() {\n const g = globalThis;\n const state = g.__cpMarquee;\n if (!state) return { restored: false, reason: "no_active_marquee" };\n clearInterval(state.interval);\n document.title = state.original;\n delete g.__cpMarquee;\n return { restored: true, original: state.original };\n }\n function networkEntriesLegacy(nArgs) {\n const all = performance.getEntriesByType("resource").map((e) => ({\n url: e.name,\n type: e.initiatorType ?? "unknown",\n duration: Math.round(e.duration)\n }));\n let entries = all;\n if (nArgs.filter) {\n const keywords = nArgs.filter.split(",").map((s) => s.trim().toLowerCase());\n entries = all.filter((e) => keywords.some((kw) => e.url.toLowerCase().includes(kw)));\n }\n return { entries, count: entries.length, source: "performance-api" };\n }\n async function waitForCondition(wArgs) {\n const timeout = wArgs.timeout ?? 1e4;\n if (wArgs.time != null) {\n await new Promise((res) => setTimeout(res, Math.min(wArgs.time * 1e3, 3e4)));\n return { waited: true, kind: "time", seconds: wArgs.time };\n }\n const deadline = Date.now() + timeout;\n const pollInterval = 200;\n if (wArgs.selector) {\n while (Date.now() < deadline) {\n if (document.querySelector(wArgs.selector)) return { waited: true, kind: "selector", selector: wArgs.selector, elapsedMs: timeout - (deadline - Date.now()) };\n await new Promise((res) => setTimeout(res, pollInterval));\n }\n return err("wait_timeout", `Selector not found within ${timeout}ms.`, { selector: wArgs.selector });\n }\n if (wArgs.text) {\n const lower = wArgs.text.toLowerCase();\n while (Date.now() < deadline) {\n if ((document.body.textContent || "").toLowerCase().includes(lower)) {\n return { waited: true, kind: "text", text: wArgs.text, elapsedMs: timeout - (deadline - Date.now()) };\n }\n await new Promise((res) => setTimeout(res, pollInterval));\n }\n return err("wait_timeout", `Text not found within ${timeout}ms.`, { text: wArgs.text });\n }\n return { error: "no_condition" };\n }\n function uploadFiles(uArgs) {\n const r = resolveTargetRich(sel());\n if ("errorEnvelope" in r) return r.errorEnvelope;\n const { el, matches_n, match_level, target } = r;\n const input = el;\n if (input.type !== "file") return err("invalid_type", "Target is not an input[type=file].", { tag: el.tagName.toLowerCase() });\n try {\n const dt = new DataTransfer();\n for (const name of uArgs.files ?? []) {\n dt.items.add(new File([""], name.split("/").pop() || name));\n }\n input.files = dt.files;\n } catch {\n }\n input.dispatchEvent(new Event("change", { bubbles: true }));\n return {\n uploaded: true,\n files: uArgs.files ?? [],\n file_names: (uArgs.files ?? []).map((f) => f.split("/").pop()),\n target,\n matches_n,\n match_level,\n multiple: input.multiple,\n accept: input.accept || null\n };\n }\n function dragElements(dArgs) {\n const r1 = resolveTargetRich(sel());\n if ("errorEnvelope" in r1) return r1.errorEnvelope;\n const r2 = resolveTargetRich({ ref: dArgs.ref2, css: dArgs.css2 });\n if ("errorEnvelope" in r2) return r2.errorEnvelope;\n const src = r1.el;\n const tgt = r2.el;\n const make = (type) => {\n const r1rect = src.getBoundingClientRect();\n return new DragEvent(type, { bubbles: true, cancelable: true, clientX: r1rect.x + r1rect.width / 2, clientY: r1rect.y + r1rect.height / 2 });\n };\n src.dispatchEvent(make("dragstart"));\n tgt.dispatchEvent(new DragEvent("dragenter", { bubbles: true }));\n tgt.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true }));\n tgt.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true }));\n src.dispatchEvent(new DragEvent("dragend", { bubbles: true }));\n return { dragged: true, source: String(sel().ref ?? sel().css ?? ""), target: String(dArgs.ref2 ?? dArgs.css2 ?? ""), source_matches_n: r1.matches_n, target_matches_n: r2.matches_n };\n }\n switch (command) {\n case "cp.state":\n return a.tree === false ? pageState() : buildStateTree();\n case "cp.snapshot":\n return buildStateTree();\n case "cp.find":\n return findElements({\n css: a.css,\n text: a.text,\n limit: a.limit,\n role: a.role,\n name: a.name,\n label: a.label,\n testid: a.testid,\n "text-max": a["text-max"]\n });\n case "cp.click":\n return clickElement();\n case "cp.type":\n return typeElement({ text: a.text });\n case "cp.goto":\n return goto({ url: a.url });\n case "cp.eval":\n return evalScript({ script: a.script });\n case "cp.fill":\n return fillElement({ text: a.text });\n case "cp.select":\n return selectElement({ label: a.label, value: a.value, option: a.option });\n case "cp.keys":\n return pressKeys({ keys: a.keys, key: a.key });\n case "cp.hover":\n return hoverElement();\n case "cp.focus":\n return focusElement();\n case "cp.dblclick":\n return dblclickElement();\n case "cp.check":\n return setChecked(true);\n case "cp.uncheck":\n return setChecked(false);\n case "cp.scroll":\n return scrollElement({ direction: a.direction, amount: a.amount });\n case "cp.back":\n return backNav();\n case "cp.get":\n return getElement({\n what: a.what,\n css: a.css,\n ref: a.ref,\n nth: a.nth,\n "as": a["as"],\n depth: a.depth,\n "children-max": a["children-max"],\n "text-max": a["text-max"],\n max: a.max,\n role: a.role,\n name: a.name,\n label: a.label,\n text: a.text,\n testid: a.testid\n });\n case "cp.extract":\n return extractMarkdown({\n "chunk-size": a["chunk-size"],\n start: a.start,\n selector: a.selector\n });\n case "cp.frames":\n return listFrames();\n case "cp.screenshot":\n return screenshotMeta();\n case "cp.network":\n return networkEntriesLegacy({ filter: a.filter });\n case "cp.wait":\n return waitForCondition({\n selector: a.selector,\n text: a.text,\n time: a.time,\n timeout: a.timeout\n });\n case "cp.upload":\n return uploadFiles({ files: a.files });\n case "cp.drag":\n return dragElements({ ref2: a.ref2, css2: a.css2 });\n case "cp.title.marquee":\n return startTitleMarquee({ text: a.text, speed: a.speed });\n case "cp.title.restore":\n return stopTitleMarquee();\n default:\n return { error: `unknown_command: ${command}` };\n }\n }\n var RUNTIME_VERSION = "0.3.0-opencli-parity";\n globalThis.__cpRunControl = runControl;\n globalThis.__cpRuntimeVersion = RUNTIME_VERSION;\n})();\n';
|
|
1003
1346
|
|
|
1004
1347
|
// src/background/cdp.ts
|
|
1005
1348
|
var attached = /* @__PURE__ */ new Set();
|
|
1349
|
+
function debuggerAttachCount() {
|
|
1350
|
+
return attached.size;
|
|
1351
|
+
}
|
|
1006
1352
|
var CDP_COMMAND_TIMEOUT_MS = 15e3;
|
|
1007
1353
|
function send(target, method, params, timeoutMs = CDP_COMMAND_TIMEOUT_MS) {
|
|
1008
1354
|
return new Promise((resolve, reject) => {
|
|
@@ -1619,157 +1965,389 @@ ${script}
|
|
|
1619
1965
|
})()`;
|
|
1620
1966
|
}
|
|
1621
1967
|
|
|
1622
|
-
//
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1968
|
+
// ../../node_modules/.pnpm/@chromepilot+kit@0.1.12_@sentry+node@10.70.0_@opentelemetry+core@2.10.0_@opentelemetry+api@1.9.1__/node_modules/@chromepilot/kit/dist/session-groups.js
|
|
1969
|
+
function createSessionGroups(deps) {
|
|
1970
|
+
const titleMaxLength = deps.titleMaxLength ?? 20;
|
|
1971
|
+
const state2 = {};
|
|
1972
|
+
let hydrated = false;
|
|
1973
|
+
let listenerInstalled = false;
|
|
1974
|
+
let queue = Promise.resolve();
|
|
1975
|
+
async function hydrate() {
|
|
1976
|
+
if (hydrated)
|
|
1977
|
+
return;
|
|
1978
|
+
try {
|
|
1979
|
+
const stored = await chrome.storage.session.get(deps.storageKey);
|
|
1980
|
+
const incoming = stored[deps.storageKey] ?? {};
|
|
1981
|
+
for (const key of Object.keys(state2))
|
|
1982
|
+
delete state2[key];
|
|
1983
|
+
for (const [sessionId, entries] of Object.entries(incoming)) {
|
|
1984
|
+
state2[sessionId] = entries.map((e) => ({ ...e, tabIds: [...e.tabIds] }));
|
|
1985
|
+
}
|
|
1986
|
+
} catch (err3) {
|
|
1987
|
+
console.warn(`${deps.logPrefix} failed to hydrate session groups`, err3);
|
|
1988
|
+
} finally {
|
|
1989
|
+
hydrated = true;
|
|
1634
1990
|
}
|
|
1635
|
-
} catch (err3) {
|
|
1636
|
-
console.warn("[HunterMate] failed to hydrate session groups", err3);
|
|
1637
|
-
} finally {
|
|
1638
|
-
hydrated = true;
|
|
1639
1991
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1992
|
+
async function persist() {
|
|
1993
|
+
try {
|
|
1994
|
+
await chrome.storage.session.set({ [deps.storageKey]: state2 });
|
|
1995
|
+
} catch (err3) {
|
|
1996
|
+
console.warn(`${deps.logPrefix} failed to persist session groups`, err3);
|
|
1997
|
+
}
|
|
1645
1998
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
} catch (err3) {
|
|
1649
|
-
console.warn("[HunterMate] failed to persist session groups", err3);
|
|
1999
|
+
function getEntries(sessionId) {
|
|
2000
|
+
return state2[sessionId] ?? [];
|
|
1650
2001
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
cache.delete(sessionId);
|
|
1658
|
-
} else {
|
|
1659
|
-
cache.set(sessionId, entries);
|
|
2002
|
+
function setEntries(sessionId, entries) {
|
|
2003
|
+
if (entries.length === 0) {
|
|
2004
|
+
delete state2[sessionId];
|
|
2005
|
+
} else {
|
|
2006
|
+
state2[sessionId] = entries;
|
|
2007
|
+
}
|
|
1660
2008
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2009
|
+
function installTabCloseListener() {
|
|
2010
|
+
if (listenerInstalled)
|
|
2011
|
+
return;
|
|
2012
|
+
listenerInstalled = true;
|
|
2013
|
+
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
2014
|
+
void (async () => {
|
|
2015
|
+
await hydrate();
|
|
2016
|
+
let dirty = false;
|
|
2017
|
+
for (const sessionId of Object.keys(state2)) {
|
|
2018
|
+
const entries = state2[sessionId];
|
|
2019
|
+
for (const entry of entries) {
|
|
2020
|
+
const before = entry.tabIds.length;
|
|
2021
|
+
entry.tabIds = entry.tabIds.filter((id) => id !== tabId);
|
|
2022
|
+
if (entry.tabIds.length !== before)
|
|
2023
|
+
dirty = true;
|
|
2024
|
+
}
|
|
2025
|
+
const cleaned = entries.filter((e) => e.tabIds.length > 0);
|
|
2026
|
+
if (cleaned.length !== entries.length)
|
|
2027
|
+
dirty = true;
|
|
2028
|
+
setEntries(sessionId, cleaned);
|
|
2029
|
+
}
|
|
2030
|
+
if (dirty)
|
|
2031
|
+
await persist();
|
|
2032
|
+
})().catch((err3) => console.warn(`${deps.logPrefix} evictClosedTab failed`, err3));
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
function hasExplicitTabTarget2(args) {
|
|
2036
|
+
return ["tab", "tabId", "page", "targetId"].some((k) => args[k] !== void 0);
|
|
2037
|
+
}
|
|
2038
|
+
function buildGroupTitle2(args, sessionId) {
|
|
2039
|
+
const raw = String(args.label ?? args.title ?? args.summary ?? args.taskTitle ?? args.taskName ?? "").trim();
|
|
2040
|
+
const compact = raw ? raw.replace(/\s+/g, " ").slice(0, titleMaxLength) : sessionId.slice(0, 8);
|
|
2041
|
+
return `${deps.titlePrefix ?? ""}${compact}`;
|
|
2042
|
+
}
|
|
2043
|
+
function normalizeGroupTitle2(title) {
|
|
2044
|
+
return title.trim().replace(/\s+/g, " ").toLowerCase();
|
|
2045
|
+
}
|
|
2046
|
+
async function findGroupByTitle(windowId, title) {
|
|
2047
|
+
const wanted = normalizeGroupTitle2(title);
|
|
2048
|
+
if (!wanted)
|
|
2049
|
+
return void 0;
|
|
2050
|
+
let groups2;
|
|
1679
2051
|
try {
|
|
1680
|
-
await chrome.tabGroups.
|
|
1681
|
-
} catch {
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
2052
|
+
groups2 = await chrome.tabGroups.query({ windowId });
|
|
2053
|
+
} catch (err3) {
|
|
2054
|
+
console.warn(`${deps.logPrefix} failed to query tab groups for adoption`, err3);
|
|
2055
|
+
return void 0;
|
|
2056
|
+
}
|
|
2057
|
+
return groups2.find((g) => normalizeGroupTitle2(g.title ?? "") === wanted);
|
|
2058
|
+
}
|
|
2059
|
+
async function ensureSessionGroupUnqueued(sessionId, title, tab) {
|
|
2060
|
+
await hydrate();
|
|
2061
|
+
installTabCloseListener();
|
|
2062
|
+
if (!tab.id || tab.windowId == null) {
|
|
2063
|
+
throw new Error("ensureSessionGroup_requires_tab_id_and_window_id");
|
|
1685
2064
|
}
|
|
1686
|
-
|
|
2065
|
+
const tabId = tab.id;
|
|
2066
|
+
const windowId = tab.windowId;
|
|
2067
|
+
const color = deps.colorForSession ? deps.colorForSession(sessionId) : "grey";
|
|
2068
|
+
const entries = getEntries(sessionId);
|
|
2069
|
+
const existing = entries.find((e) => e.windowId === windowId);
|
|
2070
|
+
if (existing) {
|
|
1687
2071
|
try {
|
|
1688
|
-
await chrome.
|
|
1689
|
-
} catch
|
|
1690
|
-
|
|
2072
|
+
await chrome.tabGroups.get(existing.groupId);
|
|
2073
|
+
} catch {
|
|
2074
|
+
setEntries(sessionId, entries.filter((e) => e.groupId !== existing.groupId));
|
|
2075
|
+
await persist();
|
|
2076
|
+
return ensureSessionGroupUnqueued(sessionId, title, tab);
|
|
1691
2077
|
}
|
|
2078
|
+
if (tab.groupId !== existing.groupId) {
|
|
2079
|
+
try {
|
|
2080
|
+
await chrome.tabs.group({ groupId: existing.groupId, tabIds: [tabId] });
|
|
2081
|
+
} catch (err3) {
|
|
2082
|
+
console.warn(`${deps.logPrefix} failed to add tab to existing session group`, {
|
|
2083
|
+
sessionId,
|
|
2084
|
+
groupId: existing.groupId,
|
|
2085
|
+
tabId,
|
|
2086
|
+
err: err3
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
if (title && title !== existing.title) {
|
|
2091
|
+
try {
|
|
2092
|
+
await chrome.tabGroups.update(existing.groupId, deps.colorForSession ? { title, color } : { title });
|
|
2093
|
+
existing.title = title;
|
|
2094
|
+
await persist();
|
|
2095
|
+
} catch (err3) {
|
|
2096
|
+
console.warn(`${deps.logPrefix} failed to update session group title`, {
|
|
2097
|
+
sessionId,
|
|
2098
|
+
groupId: existing.groupId,
|
|
2099
|
+
err: err3
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
if (!existing.tabIds.includes(tabId)) {
|
|
2104
|
+
existing.tabIds.push(tabId);
|
|
2105
|
+
await persist();
|
|
2106
|
+
}
|
|
2107
|
+
deps.onGroupInfo?.(tabId, {
|
|
2108
|
+
groupId: existing.groupId,
|
|
2109
|
+
groupColor: color,
|
|
2110
|
+
groupTitle: existing.title
|
|
2111
|
+
});
|
|
2112
|
+
return { groupId: existing.groupId, color, created: false };
|
|
1692
2113
|
}
|
|
1693
|
-
if (title
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2114
|
+
if (title) {
|
|
2115
|
+
const adoptable = await findGroupByTitle(windowId, title);
|
|
2116
|
+
if (adoptable) {
|
|
2117
|
+
if (tab.groupId !== adoptable.id) {
|
|
2118
|
+
try {
|
|
2119
|
+
await chrome.tabs.group({ groupId: adoptable.id, tabIds: [tabId] });
|
|
2120
|
+
} catch (err3) {
|
|
2121
|
+
console.warn(`${deps.logPrefix} failed to adopt tab into existing group`, {
|
|
2122
|
+
sessionId,
|
|
2123
|
+
groupId: adoptable.id,
|
|
2124
|
+
tabId,
|
|
2125
|
+
err: err3
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
const entry2 = {
|
|
2130
|
+
groupId: adoptable.id,
|
|
2131
|
+
windowId,
|
|
2132
|
+
title: adoptable.title || title,
|
|
2133
|
+
tabIds: [tabId],
|
|
2134
|
+
createdAt: Date.now()
|
|
2135
|
+
};
|
|
2136
|
+
entries.push(entry2);
|
|
2137
|
+
setEntries(sessionId, entries);
|
|
1697
2138
|
await persist();
|
|
1698
|
-
|
|
1699
|
-
|
|
2139
|
+
console.info(`${deps.logPrefix} session group adopted by title`, {
|
|
2140
|
+
sessionId,
|
|
2141
|
+
groupId: adoptable.id,
|
|
2142
|
+
title: entry2.title
|
|
2143
|
+
});
|
|
2144
|
+
deps.onGroupInfo?.(tabId, {
|
|
2145
|
+
groupId: adoptable.id,
|
|
2146
|
+
groupColor: adoptable.color,
|
|
2147
|
+
groupTitle: entry2.title
|
|
2148
|
+
});
|
|
2149
|
+
return { groupId: adoptable.id, color: adoptable.color, created: false, adopted: true };
|
|
1700
2150
|
}
|
|
1701
2151
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
await
|
|
2152
|
+
let groupId;
|
|
2153
|
+
try {
|
|
2154
|
+
groupId = await chrome.tabs.group({ tabIds: [tabId] });
|
|
2155
|
+
} catch (err3) {
|
|
2156
|
+
console.warn(`${deps.logPrefix} failed to create session group`, { sessionId, tabId, err: err3 });
|
|
2157
|
+
throw err3;
|
|
2158
|
+
}
|
|
2159
|
+
try {
|
|
2160
|
+
await chrome.tabGroups.update(groupId, {
|
|
2161
|
+
title,
|
|
2162
|
+
color,
|
|
2163
|
+
collapsed: deps.collapsedOnCreate ?? false
|
|
2164
|
+
});
|
|
2165
|
+
} catch (err3) {
|
|
2166
|
+
console.warn(`${deps.logPrefix} failed to update newly created session group`, {
|
|
2167
|
+
sessionId,
|
|
2168
|
+
groupId,
|
|
2169
|
+
err: err3
|
|
2170
|
+
});
|
|
1705
2171
|
}
|
|
1706
|
-
|
|
2172
|
+
const entry = {
|
|
2173
|
+
groupId,
|
|
2174
|
+
windowId,
|
|
2175
|
+
title,
|
|
2176
|
+
tabIds: [tabId],
|
|
2177
|
+
createdAt: Date.now()
|
|
2178
|
+
};
|
|
2179
|
+
entries.push(entry);
|
|
2180
|
+
setEntries(sessionId, entries);
|
|
2181
|
+
await persist();
|
|
2182
|
+
deps.onGroupInfo?.(tabId, { groupId, groupColor: color, groupTitle: title });
|
|
2183
|
+
return { groupId, color, created: true };
|
|
1707
2184
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
console.warn("[HunterMate] failed to create session group", { sessionId, tabId: tab.id, err: err3 });
|
|
1713
|
-
throw err3;
|
|
2185
|
+
function ensureSessionGroup2(sessionId, title, tab) {
|
|
2186
|
+
const result = queue.then(() => ensureSessionGroupUnqueued(sessionId, title, tab), () => ensureSessionGroupUnqueued(sessionId, title, tab));
|
|
2187
|
+
queue = result.catch(() => void 0);
|
|
2188
|
+
return result;
|
|
1714
2189
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2190
|
+
async function addTabToSessionGroup2(sessionId, title, tab) {
|
|
2191
|
+
if (!sessionId || !tab.id || tab.windowId == null) {
|
|
2192
|
+
return { created: false };
|
|
2193
|
+
}
|
|
2194
|
+
try {
|
|
2195
|
+
const result = await ensureSessionGroup2(sessionId, title, tab);
|
|
2196
|
+
return result;
|
|
2197
|
+
} catch (err3) {
|
|
2198
|
+
console.warn(`${deps.logPrefix} addTabToSessionGroup failed`, {
|
|
2199
|
+
sessionId,
|
|
2200
|
+
tabId: tab.id,
|
|
2201
|
+
err: err3
|
|
2202
|
+
});
|
|
2203
|
+
return { created: false };
|
|
2204
|
+
}
|
|
1723
2205
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
2206
|
+
return {
|
|
2207
|
+
hasExplicitTabTarget: hasExplicitTabTarget2,
|
|
2208
|
+
buildGroupTitle: buildGroupTitle2,
|
|
2209
|
+
ensureSessionGroup: ensureSessionGroup2,
|
|
2210
|
+
addTabToSessionGroup: addTabToSessionGroup2,
|
|
2211
|
+
normalizeGroupTitle: normalizeGroupTitle2
|
|
1730
2212
|
};
|
|
1731
|
-
entries.push(entry);
|
|
1732
|
-
await setSessionEntries(sessionId, entries);
|
|
1733
|
-
return { groupId, created: true };
|
|
1734
2213
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
2214
|
+
|
|
2215
|
+
// src/background/session-groups.ts
|
|
2216
|
+
var groups = createSessionGroups({
|
|
2217
|
+
storageKey: "hmSessionGroups",
|
|
2218
|
+
logPrefix: "[HunterMate]",
|
|
2219
|
+
titlePrefix: "\u{1F916}",
|
|
2220
|
+
titleMaxLength: 20,
|
|
2221
|
+
collapsedOnCreate: false
|
|
2222
|
+
});
|
|
2223
|
+
var {
|
|
2224
|
+
hasExplicitTabTarget,
|
|
2225
|
+
buildGroupTitle,
|
|
2226
|
+
ensureSessionGroup,
|
|
2227
|
+
addTabToSessionGroup,
|
|
2228
|
+
normalizeGroupTitle
|
|
2229
|
+
} = groups;
|
|
2230
|
+
|
|
2231
|
+
// ../../node_modules/.pnpm/@chromepilot+kit@0.1.12_@sentry+node@10.70.0_@opentelemetry+core@2.10.0_@opentelemetry+api@1.9.1__/node_modules/@chromepilot/kit/dist/tab-recycle.js
|
|
2232
|
+
function createTabRecycler(deps) {
|
|
2233
|
+
const recycleGroupTitle = deps.groupTitle ?? "\u{1F4E6}\u5DF2\u7528\u5B8C";
|
|
2234
|
+
const now = deps.now ?? Date.now;
|
|
2235
|
+
const ALARM_NAME = "tab-recycle";
|
|
2236
|
+
let alarmInstalled = false;
|
|
2237
|
+
function matchPattern(url) {
|
|
2238
|
+
if (!url)
|
|
2239
|
+
return false;
|
|
2240
|
+
return deps.urlPatterns.some((p) => p.test(url));
|
|
1745
2241
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2242
|
+
function isBusy(tab) {
|
|
2243
|
+
if (tab.active || tab.audible)
|
|
2244
|
+
return true;
|
|
2245
|
+
if (deps.isTabBusy)
|
|
2246
|
+
return deps.isTabBusy(tab, now());
|
|
2247
|
+
return false;
|
|
2248
|
+
}
|
|
2249
|
+
async function sweep() {
|
|
2250
|
+
const tabs = await chrome.tabs.query({});
|
|
2251
|
+
const matched = tabs.filter((t) => t.id != null && matchPattern(t.url));
|
|
2252
|
+
const candidates = matched.filter((t) => !isBusy(t));
|
|
2253
|
+
const result = {
|
|
2254
|
+
moved: 0,
|
|
2255
|
+
busy: matched.length - candidates.length
|
|
2256
|
+
};
|
|
2257
|
+
if (candidates.length === 0) {
|
|
2258
|
+
deps.onSweepResult?.(result);
|
|
2259
|
+
return result;
|
|
1757
2260
|
}
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1760
|
-
|
|
2261
|
+
const byWindow = /* @__PURE__ */ new Map();
|
|
2262
|
+
const tabById = /* @__PURE__ */ new Map();
|
|
2263
|
+
for (const t of candidates) {
|
|
2264
|
+
const w = t.windowId ?? -1;
|
|
2265
|
+
if (!byWindow.has(w))
|
|
2266
|
+
byWindow.set(w, []);
|
|
2267
|
+
byWindow.get(w).push(t.id);
|
|
2268
|
+
tabById.set(t.id, t);
|
|
1761
2269
|
}
|
|
1762
|
-
|
|
2270
|
+
for (const [windowId, tabIds] of byWindow) {
|
|
2271
|
+
try {
|
|
2272
|
+
const existing = (await chrome.tabGroups.query({ windowId })).find((g) => g.title === recycleGroupTitle);
|
|
2273
|
+
const freshIds = existing ? tabIds.filter((id) => tabById.get(id)?.groupId !== existing.id) : tabIds;
|
|
2274
|
+
if (freshIds.length === 0)
|
|
2275
|
+
continue;
|
|
2276
|
+
let groupId;
|
|
2277
|
+
if (existing) {
|
|
2278
|
+
groupId = existing.id;
|
|
2279
|
+
await chrome.tabs.group({ groupId, tabIds: freshIds });
|
|
2280
|
+
} else {
|
|
2281
|
+
groupId = await chrome.tabs.group({ tabIds: [freshIds[0]] });
|
|
2282
|
+
await chrome.tabGroups.update(groupId, {
|
|
2283
|
+
title: recycleGroupTitle,
|
|
2284
|
+
color: "grey",
|
|
2285
|
+
collapsed: true
|
|
2286
|
+
});
|
|
2287
|
+
const rest = freshIds.slice(1);
|
|
2288
|
+
if (rest.length)
|
|
2289
|
+
await chrome.tabs.group({ groupId, tabIds: rest });
|
|
2290
|
+
}
|
|
2291
|
+
result.moved += freshIds.length;
|
|
2292
|
+
result.groupId = groupId;
|
|
2293
|
+
console.info(`${deps.logPrefix} tab-recycle swept`, { windowId, count: freshIds.length, groupId });
|
|
2294
|
+
} catch (err3) {
|
|
2295
|
+
console.warn(`${deps.logPrefix} tab-recycle sweep failed`, { windowId, err: err3 });
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
deps.onSweepResult?.(result);
|
|
2299
|
+
return result;
|
|
1763
2300
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
2301
|
+
function installAlarm(periodMinutes = 10) {
|
|
2302
|
+
if (alarmInstalled)
|
|
2303
|
+
return;
|
|
2304
|
+
alarmInstalled = true;
|
|
2305
|
+
chrome.alarms.create(ALARM_NAME, { periodInMinutes: periodMinutes });
|
|
2306
|
+
chrome.alarms.onAlarm.addListener((alarm) => {
|
|
2307
|
+
if (alarm.name !== ALARM_NAME)
|
|
2308
|
+
return;
|
|
2309
|
+
void sweep().catch((err3) => console.warn(`${deps.logPrefix} tab-recycle alarm sweep failed`, err3));
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
async function findReusableTab(url) {
|
|
2313
|
+
if (!/^https?:/.test(url))
|
|
2314
|
+
return void 0;
|
|
2315
|
+
const tabs = await chrome.tabs.query({ url });
|
|
2316
|
+
const idle = tabs.find((t) => t.id != null && !t.active && !t.audible && !isBusy(t));
|
|
2317
|
+
return idle?.id;
|
|
1766
2318
|
}
|
|
2319
|
+
return { sweep, installAlarm, findReusableTab, recycleGroupTitle };
|
|
1767
2320
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
2321
|
+
|
|
2322
|
+
// src/background/tab-recycle.ts
|
|
2323
|
+
var DETAIL_PATHS = [
|
|
2324
|
+
"https://h.liepin.com/resume/showresumedetail/",
|
|
2325
|
+
"https://h.liepin.com/job/gethjobdetail"
|
|
2326
|
+
];
|
|
2327
|
+
var tabRecycler = createTabRecycler({
|
|
2328
|
+
logPrefix: "[HunterMate]",
|
|
2329
|
+
urlPatterns: DETAIL_PATHS.map((p) => new RegExp("^" + p.replace(/[/.]/g, (c) => c === "/" ? "\\/" : "\\.")))
|
|
1770
2330
|
});
|
|
2331
|
+
function isDetailUrl(url) {
|
|
2332
|
+
return DETAIL_PATHS.some((p) => url.startsWith(p));
|
|
2333
|
+
}
|
|
2334
|
+
async function findDetailTabToReuse(url) {
|
|
2335
|
+
if (!isDetailUrl(url)) return void 0;
|
|
2336
|
+
const prefix = DETAIL_PATHS.find((p) => url.startsWith(p));
|
|
2337
|
+
const candidates = await chrome.tabs.query({ url: prefix + "*" }).catch(() => []);
|
|
2338
|
+
const idle = candidates.find((t) => t.id != null && !t.active && !t.audible);
|
|
2339
|
+
return idle?.id;
|
|
2340
|
+
}
|
|
1771
2341
|
|
|
1772
2342
|
// src/background/control.ts
|
|
2343
|
+
var RUNTIME_BRIDGE_FILE = "content/control-runtime.js";
|
|
2344
|
+
async function invokeRuntimeBridge(command, args) {
|
|
2345
|
+
const rt = globalThis.__hmControlRuntime;
|
|
2346
|
+
if (!rt || typeof rt.runControl !== "function") {
|
|
2347
|
+
return { error: "runtime_bridge_missing: content/control-runtime.js \u672A\u6CE8\u5165\u6210\u529F" };
|
|
2348
|
+
}
|
|
2349
|
+
return await Promise.resolve(rt.runControl(command, args));
|
|
2350
|
+
}
|
|
1773
2351
|
var boundTabId = null;
|
|
1774
2352
|
async function getActiveTab() {
|
|
1775
2353
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
@@ -1810,10 +2388,35 @@ async function frameListCdp(tabId) {
|
|
|
1810
2388
|
walk(tree?.frameTree);
|
|
1811
2389
|
return { frames: out };
|
|
1812
2390
|
}
|
|
1813
|
-
function buildRuntimeExpression(command, args) {
|
|
1814
|
-
return '(() => { try { if (typeof globalThis.__cpRunControl !== "function" || globalThis.__cpRuntimeVersion !== ' + JSON.stringify(RUNTIME_VERSION) + ") {\n" + control_runtime_source_default + "\n} const r = globalThis.__cpRunControl(" + JSON.stringify(command) + ", " + JSON.stringify(args) + '); if (r && typeof r.then === "function") { return r; } return r; } catch (e) { return { __cpError: String((e && e.message) || e) }; } })()';
|
|
1815
|
-
}
|
|
1816
2391
|
var marqueeCounters = /* @__PURE__ */ new Map();
|
|
2392
|
+
var MARQUEE_START_PAGE = (text) => {
|
|
2393
|
+
const g = globalThis;
|
|
2394
|
+
if (g.__cpMarquee) {
|
|
2395
|
+
clearInterval(g.__cpMarquee.interval);
|
|
2396
|
+
} else {
|
|
2397
|
+
g.__cpMarquee = { original: document.title, interval: 0 };
|
|
2398
|
+
}
|
|
2399
|
+
const prefix = text ?? "\u{1F916}";
|
|
2400
|
+
const base = g.__cpMarquee.original;
|
|
2401
|
+
const full = `${prefix} ${base}`;
|
|
2402
|
+
const speed = 300;
|
|
2403
|
+
let pos = 0;
|
|
2404
|
+
const tick = () => {
|
|
2405
|
+
const display = full.slice(pos) + " " + full.slice(0, pos);
|
|
2406
|
+
document.title = display.slice(0, 60);
|
|
2407
|
+
pos = (pos + 1) % (full.length + 1 || 1);
|
|
2408
|
+
};
|
|
2409
|
+
tick();
|
|
2410
|
+
g.__cpMarquee.interval = window.setInterval(tick, speed);
|
|
2411
|
+
};
|
|
2412
|
+
var MARQUEE_STOP_PAGE = () => {
|
|
2413
|
+
const g = globalThis;
|
|
2414
|
+
const state2 = g.__cpMarquee;
|
|
2415
|
+
if (!state2) return;
|
|
2416
|
+
clearInterval(state2.interval);
|
|
2417
|
+
document.title = state2.original;
|
|
2418
|
+
delete g.__cpMarquee;
|
|
2419
|
+
};
|
|
1817
2420
|
async function startTitleMarquee(tabId, sessionId, text) {
|
|
1818
2421
|
if (!isAttachableUrl((await chrome.tabs.get(tabId).catch(() => null))?.url)) {
|
|
1819
2422
|
return;
|
|
@@ -1825,8 +2428,11 @@ async function startTitleMarquee(tabId, sessionId, text) {
|
|
|
1825
2428
|
}
|
|
1826
2429
|
marqueeCounters.set(tabId, { count: 1, sessionId });
|
|
1827
2430
|
try {
|
|
1828
|
-
|
|
1829
|
-
|
|
2431
|
+
await chrome.scripting.executeScript({
|
|
2432
|
+
target: { tabId },
|
|
2433
|
+
func: MARQUEE_START_PAGE,
|
|
2434
|
+
args: [text ?? "\u{1F916}"]
|
|
2435
|
+
});
|
|
1830
2436
|
} catch (err3) {
|
|
1831
2437
|
console.warn("[HunterMate] startTitleMarquee failed", { tabId, error: err3.message });
|
|
1832
2438
|
}
|
|
@@ -1838,8 +2444,7 @@ async function stopTitleMarquee(tabId, _sessionId) {
|
|
|
1838
2444
|
if (entry.count > 0) return;
|
|
1839
2445
|
marqueeCounters.delete(tabId);
|
|
1840
2446
|
try {
|
|
1841
|
-
|
|
1842
|
-
await cdpEvaluateTransient(tabId, expression);
|
|
2447
|
+
await chrome.scripting.executeScript({ target: { tabId }, func: MARQUEE_STOP_PAGE, args: [] });
|
|
1843
2448
|
} catch (err3) {
|
|
1844
2449
|
console.warn("[HunterMate] stopTitleMarquee failed", { tabId, error: err3.message });
|
|
1845
2450
|
}
|
|
@@ -2069,7 +2674,7 @@ async function executeControlCommand(rawCommand, rawArgs, options = {}) {
|
|
|
2069
2674
|
return err2(e instanceof Error ? e.message : String(e));
|
|
2070
2675
|
}
|
|
2071
2676
|
}
|
|
2072
|
-
if (command === "cp.eval" && typeof controlArgs.script === "string") {
|
|
2677
|
+
if (command === "cp.eval" && typeof controlArgs.script === "string" && controlArgs.debuggerFree !== true) {
|
|
2073
2678
|
const script = controlArgs.script;
|
|
2074
2679
|
const frame = controlArgs.frame;
|
|
2075
2680
|
const tabId = tab.id;
|
|
@@ -2129,7 +2734,7 @@ async function executeControlCommand(rawCommand, rawArgs, options = {}) {
|
|
|
2129
2734
|
if (command.startsWith("hm.")) {
|
|
2130
2735
|
return { status: "error", code: "not_implemented", message: `${command} is not implemented yet` };
|
|
2131
2736
|
}
|
|
2132
|
-
if (isAttachableUrl(tab.url)) {
|
|
2737
|
+
if (isAttachableUrl(tab.url) && controlArgs.debuggerFree !== true) {
|
|
2133
2738
|
log("debug", "control running via CDP runtime", { command, tabId: tab.id });
|
|
2134
2739
|
try {
|
|
2135
2740
|
const result = await cdpEvaluate(tab.id, buildInjectionExpression(command, controlArgs));
|
|
@@ -2147,9 +2752,13 @@ async function executeControlCommand(rawCommand, rawArgs, options = {}) {
|
|
|
2147
2752
|
`Cannot access a ${tab.url.split("/")[0]}// page for control commands. Open the target page in a normal tab first.`
|
|
2148
2753
|
);
|
|
2149
2754
|
}
|
|
2755
|
+
await chrome.scripting.executeScript({
|
|
2756
|
+
target: { tabId: tab.id },
|
|
2757
|
+
files: [RUNTIME_BRIDGE_FILE]
|
|
2758
|
+
});
|
|
2150
2759
|
const [injection] = await chrome.scripting.executeScript({
|
|
2151
2760
|
target: { tabId: tab.id },
|
|
2152
|
-
func:
|
|
2761
|
+
func: invokeRuntimeBridge,
|
|
2153
2762
|
args: [command, controlArgs]
|
|
2154
2763
|
});
|
|
2155
2764
|
const inj = injection;
|
|
@@ -3443,10 +4052,16 @@ async function handleTabCommand(command, args, log) {
|
|
|
3443
4052
|
case "cp.tab.new": {
|
|
3444
4053
|
const url = typeof args.url === "string" && args.url ? args.url : "about:blank";
|
|
3445
4054
|
const active = args.active === true;
|
|
4055
|
+
const reusable = await findDetailTabToReuse(url).catch(() => void 0);
|
|
4056
|
+
if (reusable != null) {
|
|
4057
|
+
await chrome.tabs.update(reusable, { url, active });
|
|
4058
|
+
log("info", "cp.tab.new reused detail tab", { page: String(reusable), url, active });
|
|
4059
|
+
return { page: String(reusable), url, reused: true };
|
|
4060
|
+
}
|
|
3446
4061
|
log("debug", "cp.tab.new creating tab", { url, active });
|
|
3447
4062
|
const tab = await chrome.tabs.create({ url, active });
|
|
3448
4063
|
log("info", "cp.tab.new created", { page: String(tab.id), url: tab.url ?? url, active });
|
|
3449
|
-
return { page: String(tab.id), url: tab.url ?? url };
|
|
4064
|
+
return { page: String(tab.id), url: tab.url ?? url, reused: false };
|
|
3450
4065
|
}
|
|
3451
4066
|
case "cp.tab.open": {
|
|
3452
4067
|
const url = typeof args.url === "string" && args.url.trim() ? args.url.trim() : "";
|
|
@@ -3522,6 +4137,7 @@ function etldPlusOne(hostname) {
|
|
|
3522
4137
|
}
|
|
3523
4138
|
|
|
3524
4139
|
// src/background/sites.ts
|
|
4140
|
+
var import_shared = __toESM(require_dist());
|
|
3525
4141
|
var DYNAMIC_SCRIPT_ID = "hm-auto-capture";
|
|
3526
4142
|
var RECRUITING_HINTS = [
|
|
3527
4143
|
"jobs",
|
|
@@ -3556,6 +4172,7 @@ var PATH_CLASS_HINTS = [
|
|
|
3556
4172
|
];
|
|
3557
4173
|
var sitePolicyMap = /* @__PURE__ */ new Map();
|
|
3558
4174
|
function getSitePolicy(hostname) {
|
|
4175
|
+
if ((0, import_shared.isHarnessInternalHostname)(hostname)) return { record: false, parse: false };
|
|
3559
4176
|
return sitePolicyMap.get(etldPlusOne(hostname)) ?? { record: false, parse: false };
|
|
3560
4177
|
}
|
|
3561
4178
|
async function syncSites() {
|
|
@@ -3828,13 +4445,13 @@ async function createHttpTransport(opts) {
|
|
|
3828
4445
|
}
|
|
3829
4446
|
|
|
3830
4447
|
// src/shared/queue.ts
|
|
3831
|
-
var
|
|
4448
|
+
var STORAGE_KEY = "hunter-mate:logs-pending";
|
|
3832
4449
|
var ChromeStorageLogQueue = class {
|
|
3833
4450
|
buffer = [];
|
|
3834
4451
|
nextId = 1;
|
|
3835
4452
|
maxBytes = 2 * 1024 * 1024;
|
|
3836
4453
|
async load() {
|
|
3837
|
-
const stored = await chromeStorageLocalGet(
|
|
4454
|
+
const stored = await chromeStorageLocalGet(STORAGE_KEY);
|
|
3838
4455
|
if (!stored) return;
|
|
3839
4456
|
try {
|
|
3840
4457
|
const parsed = JSON.parse(stored);
|
|
@@ -3859,10 +4476,10 @@ var ChromeStorageLogQueue = class {
|
|
|
3859
4476
|
}
|
|
3860
4477
|
async persist() {
|
|
3861
4478
|
try {
|
|
3862
|
-
await chromeStorageLocalSet(
|
|
4479
|
+
await chromeStorageLocalSet(STORAGE_KEY, JSON.stringify(this.buffer));
|
|
3863
4480
|
} catch {
|
|
3864
4481
|
this.buffer.splice(0, Math.ceil(this.buffer.length * 0.5));
|
|
3865
|
-
await chromeStorageLocalSet(
|
|
4482
|
+
await chromeStorageLocalSet(STORAGE_KEY, JSON.stringify(this.buffer));
|
|
3866
4483
|
}
|
|
3867
4484
|
}
|
|
3868
4485
|
size() {
|
|
@@ -3887,7 +4504,7 @@ var ChromeStorageLogQueue = class {
|
|
|
3887
4504
|
async clear() {
|
|
3888
4505
|
this.buffer = [];
|
|
3889
4506
|
this.nextId = 1;
|
|
3890
|
-
await chromeStorageLocalRemove(
|
|
4507
|
+
await chromeStorageLocalRemove(STORAGE_KEY);
|
|
3891
4508
|
}
|
|
3892
4509
|
};
|
|
3893
4510
|
function chromeStorageLocalGet(key) {
|
|
@@ -4008,7 +4625,7 @@ async function readDiagRing() {
|
|
|
4008
4625
|
}
|
|
4009
4626
|
}
|
|
4010
4627
|
|
|
4011
|
-
// ../../node_modules/.pnpm/@chromepilot+kit@0.1.
|
|
4628
|
+
// ../../node_modules/.pnpm/@chromepilot+kit@0.1.12_@sentry+node@10.70.0_@opentelemetry+core@2.10.0_@opentelemetry+api@1.9.1__/node_modules/@chromepilot/kit/dist/offscreen.js
|
|
4012
4629
|
var OFFSCREEN_MSG = {
|
|
4013
4630
|
/** SW → offscreen:注入身份(getManifest 在 offscreen 不可用) */
|
|
4014
4631
|
INIT: "offscreen:init",
|
|
@@ -4221,6 +4838,95 @@ function createSwOffscreenRelay(opts) {
|
|
|
4221
4838
|
};
|
|
4222
4839
|
}
|
|
4223
4840
|
|
|
4841
|
+
// ../../node_modules/.pnpm/@chromepilot+kit@0.1.12_@sentry+node@10.70.0_@opentelemetry+core@2.10.0_@opentelemetry+api@1.9.1__/node_modules/@chromepilot/kit/dist/hard-reload.js
|
|
4842
|
+
var OFFSCREEN_CONTEXT_TYPE = "OFFSCREEN_DOCUMENT";
|
|
4843
|
+
function createHardReloader(deps) {
|
|
4844
|
+
const minUptimeMs = deps.minUptimeMs ?? 3e3;
|
|
4845
|
+
const settleMs = deps.settleMs ?? 500;
|
|
4846
|
+
const now = deps.now ?? Date.now;
|
|
4847
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
4848
|
+
const reloadFn = deps.reload ?? (() => chrome.runtime.reload());
|
|
4849
|
+
const getContexts = deps.getContexts ?? ((filter) => chrome.runtime.getContexts(filter));
|
|
4850
|
+
const closeDocument = deps.closeDocument ?? (() => chrome.offscreen.closeDocument());
|
|
4851
|
+
function canHardReload() {
|
|
4852
|
+
if (now() - deps.startedAt < minUptimeMs)
|
|
4853
|
+
return { ok: false, reason: "boot_window" };
|
|
4854
|
+
if (deps.hasDebuggerAttached())
|
|
4855
|
+
return { ok: false, reason: "debugger_attached" };
|
|
4856
|
+
return { ok: true };
|
|
4857
|
+
}
|
|
4858
|
+
async function teardownOffscreen() {
|
|
4859
|
+
try {
|
|
4860
|
+
await closeDocument();
|
|
4861
|
+
} catch {
|
|
4862
|
+
}
|
|
4863
|
+
for (let i = 0; i < 6; i++) {
|
|
4864
|
+
const contexts = await getContexts({
|
|
4865
|
+
contextTypes: [OFFSCREEN_CONTEXT_TYPE]
|
|
4866
|
+
}).catch(() => []);
|
|
4867
|
+
if (contexts.length === 0)
|
|
4868
|
+
return true;
|
|
4869
|
+
await sleep2(250);
|
|
4870
|
+
}
|
|
4871
|
+
return false;
|
|
4872
|
+
}
|
|
4873
|
+
async function hardReload() {
|
|
4874
|
+
const verdict = canHardReload();
|
|
4875
|
+
if (!verdict.ok)
|
|
4876
|
+
return verdict;
|
|
4877
|
+
if (!await teardownOffscreen()) {
|
|
4878
|
+
return { ok: false, reason: "offscreen_teardown_failed" };
|
|
4879
|
+
}
|
|
4880
|
+
await sleep2(settleMs);
|
|
4881
|
+
console.info(`${deps.logPrefix} hard reload: checks passed, calling runtime.reload()`);
|
|
4882
|
+
reloadFn();
|
|
4883
|
+
return { ok: true };
|
|
4884
|
+
}
|
|
4885
|
+
return { canHardReload, hardReload };
|
|
4886
|
+
}
|
|
4887
|
+
async function togglePeerExtension(management, namePattern, sleep2 = (ms) => new Promise((r) => setTimeout(r, ms)), sendWake) {
|
|
4888
|
+
const all = await management.getAll();
|
|
4889
|
+
const selfId = chrome.runtime.id;
|
|
4890
|
+
const peer = all.find((e) => e.id !== selfId && e.installType === "development" && (namePattern.test(e.name) || namePattern.test(e.shortName ?? "")));
|
|
4891
|
+
if (!peer)
|
|
4892
|
+
return { ok: false, reason: "peer_not_found" };
|
|
4893
|
+
try {
|
|
4894
|
+
await management.setEnabled(peer.id, false);
|
|
4895
|
+
await sleep2(400);
|
|
4896
|
+
await management.setEnabled(peer.id, true);
|
|
4897
|
+
const wake = sendWake ?? ((id) => chrome.runtime.sendMessage(id, { type: "__cp_peer_wake__" }));
|
|
4898
|
+
const wakeWithTimeout = async (id, ms) => {
|
|
4899
|
+
await Promise.race([
|
|
4900
|
+
wake(id),
|
|
4901
|
+
sleep2(ms).then(() => {
|
|
4902
|
+
throw new Error("wake timeout");
|
|
4903
|
+
})
|
|
4904
|
+
]);
|
|
4905
|
+
};
|
|
4906
|
+
try {
|
|
4907
|
+
await wakeWithTimeout(peer.id, 5e3);
|
|
4908
|
+
await sleep2(300);
|
|
4909
|
+
void wakeWithTimeout(peer.id, 5e3).catch(() => {
|
|
4910
|
+
});
|
|
4911
|
+
} catch {
|
|
4912
|
+
}
|
|
4913
|
+
return { ok: true, peerId: peer.id };
|
|
4914
|
+
} catch {
|
|
4915
|
+
return { ok: false, reason: "toggle_failed", peerId: peer.id };
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
// src/background/extension-reload.ts
|
|
4920
|
+
var SW_STARTED_AT = Date.now();
|
|
4921
|
+
var hardReloader = createHardReloader({
|
|
4922
|
+
logPrefix: "[HunterMate]",
|
|
4923
|
+
hasDebuggerAttached: () => debuggerAttachCount() > 0,
|
|
4924
|
+
startedAt: SW_STARTED_AT
|
|
4925
|
+
});
|
|
4926
|
+
function reloadChromepilot() {
|
|
4927
|
+
return togglePeerExtension(chrome.management, /ChromePilot/);
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4224
4930
|
// src/background/service-worker.ts
|
|
4225
4931
|
diag("sw", "sw-start", { reason: "top-level-eval" });
|
|
4226
4932
|
self.addEventListener("error", (e) => diag("sw", "sw-error", { message: e.message, src: (e.filename || "").split("/").pop() }));
|
|
@@ -4405,7 +5111,22 @@ async function handleServerMessage(data) {
|
|
|
4405
5111
|
const startedAt = Date.now();
|
|
4406
5112
|
sendLog("info", "extension handling control command", { command: data.command, requestId: data.requestId, params: data.params });
|
|
4407
5113
|
let outcome;
|
|
4408
|
-
|
|
5114
|
+
let startSelfHardReloadAfterAck = false;
|
|
5115
|
+
if (data.command === "hm.system.reloadExtension") {
|
|
5116
|
+
const target = data.params?.target === "cp" ? "cp" : "self";
|
|
5117
|
+
if (target === "cp") {
|
|
5118
|
+
const toggleResult = await reloadChromepilot();
|
|
5119
|
+
outcome = toggleResult.ok ? { status: "ok", result: { ok: true, via: "cp", name: toggleResult.name } } : { status: "error", code: "peer_reload_failed", message: toggleResult.reason ?? "toggle ChromePilot failed" };
|
|
5120
|
+
} else {
|
|
5121
|
+
const verdict = hardReloader.canHardReload();
|
|
5122
|
+
if (!verdict.ok) {
|
|
5123
|
+
outcome = { status: "error", code: "reload_refused", message: `hard reload refused: ${verdict.reason}` };
|
|
5124
|
+
} else {
|
|
5125
|
+
outcome = { status: "ok", result: { ok: true, via: "self", reloading: true } };
|
|
5126
|
+
startSelfHardReloadAfterAck = true;
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
} else if (data.command === "hm.status") {
|
|
4409
5130
|
const manifest = chrome.runtime.getManifest();
|
|
4410
5131
|
outcome = {
|
|
4411
5132
|
status: "ok",
|
|
@@ -4451,6 +5172,9 @@ async function handleServerMessage(data) {
|
|
|
4451
5172
|
};
|
|
4452
5173
|
sendToOffscreen(JSON.stringify(resp));
|
|
4453
5174
|
sendLog("debug", "cmd_response sent", { requestId: data.requestId, status: resp.status, durationMs: resp.durationMs });
|
|
5175
|
+
if (startSelfHardReloadAfterAck) {
|
|
5176
|
+
void hardReloader.hardReload();
|
|
5177
|
+
}
|
|
4454
5178
|
}
|
|
4455
5179
|
}
|
|
4456
5180
|
var RECONNECT_ALARM_NAME = "hm:reconnect";
|
|
@@ -4493,6 +5217,8 @@ chrome.runtime.onStartup.addListener(() => {
|
|
|
4493
5217
|
setInterval(() => {
|
|
4494
5218
|
checkHttpHealth();
|
|
4495
5219
|
void ensureOffscreen();
|
|
5220
|
+
const TAB_RECYCLE_SWEEP_ENABLED = false;
|
|
5221
|
+
if (TAB_RECYCLE_SWEEP_ENABLED) tabRecycler.installAlarm(10);
|
|
4496
5222
|
broadcastState();
|
|
4497
5223
|
}, 2e4);
|
|
4498
5224
|
setInterval(() => {
|