@lincoln504/pi-research 1.5.2 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/{skills → agent-skill}/pi-research/README.md +5 -9
- package/{skills → agent-skill}/pi-research/SKILL.md +5 -4
- package/{skills → agent-skill}/pi-research/scripts/run.mjs +1 -1
- package/dist/cli.mjs +158 -129
- package/dist/thread-worker.mjs +105 -17
- package/docs/AGENT-SKILL.md +26 -40
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CHANGELOG.md +52 -0
- package/docs/CONFIGURATION.md +4 -4
- package/docs/PI-EXTENSION.md +1 -1
- package/docs/SDK.md +73 -10
- package/package.json +9 -21
- package/scripts/build.cjs +4 -4
- package/scripts/cleanup.cjs +8 -3
- package/scripts/prepare.cjs +1 -1
- package/src/cli.ts +1 -1
- package/src/infrastructure/browser/browser-client.ts +24 -6
- package/src/infrastructure/browser/browser-error-utils.ts +53 -0
- package/src/infrastructure/browser/browser-server.ts +9 -1
- package/src/infrastructure/browser/thread-worker-browser.ts +4 -1
- package/src/infrastructure/browser/thread-worker-messaging.ts +186 -3
- package/src/infrastructure/browser/thread-worker.mjs +105 -17
- package/src/knowledge/store-operations.ts +18 -7
- package/src/knowledge/store.ts +21 -3
- package/src/skill-install/skill-installer.ts +59 -21
- package/src/web-research/scraper-utils.ts +4 -36
- package/src/youtube/potoken.ts +47 -34
- /package/{skills → agent-skill}/pi-research/references/configuration.md +0 -0
package/src/knowledge/store.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { MigrationStrategy, MigrationResult } from './migration.ts';
|
|
|
23
23
|
import { metrics } from '../utils/metrics.ts';
|
|
24
24
|
import { createStoreTable, CURRENT_SCHEMA_VERSION } from './store-schema.ts';
|
|
25
25
|
import { addDocumentsToStore, assertValidEmbeddingVectors, searchStore, findDocumentsByUrl, findRelevantUrls } from './store-operations.ts';
|
|
26
|
+
import type { AddDocumentsOutcome } from './store-operations.ts';
|
|
26
27
|
import { isEmbedderUnreachable } from './embedder-utils.ts';
|
|
27
28
|
import { ServiceLifecycle } from '../core/service-registry.ts';
|
|
28
29
|
import { ServiceNames } from '../core/service-interfaces.ts';
|
|
@@ -713,13 +714,14 @@ export class KnowledgeStore implements IKnowledgeStore {
|
|
|
713
714
|
// Now check isClosing after registering
|
|
714
715
|
if (this.isClosing) {
|
|
715
716
|
logger.warn(`[store] Skipping addDocuments — store is closing`);
|
|
717
|
+
metrics.increment('knowledge_store_add_documents_total', 1, { status: 'ignored_closing' });
|
|
716
718
|
return;
|
|
717
719
|
}
|
|
718
720
|
|
|
719
721
|
let table = await this.getFreshTable();
|
|
720
722
|
const workspace = this.getWorkspace();
|
|
721
723
|
|
|
722
|
-
await this.withEmbedderReconnect(async (embedder) => {
|
|
724
|
+
const outcome = await this.withEmbedderReconnect<AddDocumentsOutcome>(async (embedder) => {
|
|
723
725
|
let retryCount = 0;
|
|
724
726
|
const MAX_RETRIES = 5;
|
|
725
727
|
const BASE_DELAY = 100;
|
|
@@ -727,7 +729,7 @@ export class KnowledgeStore implements IKnowledgeStore {
|
|
|
727
729
|
while (retryCount <= MAX_RETRIES) {
|
|
728
730
|
try {
|
|
729
731
|
const isGlobal = this.options.knowledgeMode === 'global';
|
|
730
|
-
|
|
732
|
+
return await addDocumentsToStore(
|
|
731
733
|
table,
|
|
732
734
|
docs,
|
|
733
735
|
embedder,
|
|
@@ -735,7 +737,6 @@ export class KnowledgeStore implements IKnowledgeStore {
|
|
|
735
737
|
workspace,
|
|
736
738
|
isGlobal
|
|
737
739
|
);
|
|
738
|
-
return;
|
|
739
740
|
} catch (err) {
|
|
740
741
|
// Detect commit conflicts typical of cross-process write contention
|
|
741
742
|
if (isLanceCommitConflict(err)) {
|
|
@@ -758,7 +759,24 @@ export class KnowledgeStore implements IKnowledgeStore {
|
|
|
758
759
|
throw err;
|
|
759
760
|
}
|
|
760
761
|
}
|
|
762
|
+
// Unreachable: the loop only exits via return or throw. Present so the
|
|
763
|
+
// outcome type stays non-optional for the accounting below.
|
|
764
|
+
throw new Error('[store] addDocuments retry loop exited without result');
|
|
761
765
|
});
|
|
766
|
+
|
|
767
|
+
// Exactly one outcome per logical addDocuments call. Attempts that failed and
|
|
768
|
+
// were then recovered by the reconnect/conflict retries are not counted here
|
|
769
|
+
// and are not logged at ERROR — only a write that is actually abandoned is.
|
|
770
|
+
// An empty batch is a no-op and is not counted at all, as before.
|
|
771
|
+
if (outcome !== 'empty') {
|
|
772
|
+
metrics.increment('knowledge_store_add_documents_total', 1, {
|
|
773
|
+
status: outcome === 'ignored_closing' ? 'ignored_closing' : 'success',
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
} catch (err) {
|
|
777
|
+
metrics.increment('knowledge_store_add_documents_total', 1, { status: 'error' });
|
|
778
|
+
logger.error('[store] Failed to add documents:', err);
|
|
779
|
+
throw err;
|
|
762
780
|
} finally {
|
|
763
781
|
this.activeWrites.delete(writePromise);
|
|
764
782
|
writeResolve!();
|
|
@@ -49,8 +49,11 @@ export interface HarnessDef {
|
|
|
49
49
|
export const HARNESSES: readonly HarnessDef[] = [
|
|
50
50
|
{ id: 'claude', label: 'Claude', baseDir: '.claude', skillsDir: path.join('.claude', 'skills'), confidence: 'confirmed' },
|
|
51
51
|
{ id: 'openclaw', label: 'OpenClaw', baseDir: '.openclaw', skillsDir: path.join('.openclaw', 'skills'), confidence: 'confirmed', note: 'OpenClaw reads ~/.openclaw/skills as a managed skill root and accepts symlinked skill folders (docs.openclaw.ai/tools/skills).' },
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// pi's own skill root is under its agent dir (~/.pi/agent/skills), NOT ~/.pi/skills.
|
|
53
|
+
// Listed for DETECTION only — pi is never an install target (see SKILL_AGENT_TARGETS):
|
|
54
|
+
// the host already exposes research as a native tool, so a skill there would just
|
|
55
|
+
// duplicate it through a slower subprocess.
|
|
56
|
+
{ id: 'pi', label: 'pi', baseDir: '.pi', skillsDir: path.join('.pi', 'agent', 'skills'), confidence: 'confirmed' },
|
|
54
57
|
{ id: 'codex', label: 'OpenAI Codex CLI', baseDir: '.codex', skillsDir: path.join('.codex', 'skills'), confidence: 'unverified', note: 'Codex skills support is emerging; path not confirmed by official docs.' },
|
|
55
58
|
{ id: 'agents', label: 'Cross-tool (~/.agents/skills)', baseDir: '.agents', skillsDir: path.join('.agents', 'skills'), confidence: 'unverified', note: 'Proposed Universal Agents convention; not yet a ratified standard.' },
|
|
56
59
|
] as const;
|
|
@@ -58,6 +61,31 @@ export const HARNESSES: readonly HarnessDef[] = [
|
|
|
58
61
|
const SKILL_NAME = 'pi-research';
|
|
59
62
|
const PACKAGE_NAME = '@lincoln504/pi-research';
|
|
60
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Directory inside this package that holds the shipped skill.
|
|
66
|
+
*
|
|
67
|
+
* Deliberately NOT `skills/`: pi treats a package-root `skills/` directory as a
|
|
68
|
+
* resource root and convention-scans it whenever its manifest omits a `skills`
|
|
69
|
+
* key, so the old name made pi load this skill — a slower duplicate of the
|
|
70
|
+
* extension's own native research tool — as soon as anything rewrote the
|
|
71
|
+
* package's settings entry into object form (which `pi config` does on any
|
|
72
|
+
* toggle). The name must stay outside pi's reserved resource types
|
|
73
|
+
* (extensions, skills, prompts, themes).
|
|
74
|
+
*/
|
|
75
|
+
const SKILL_SOURCE_DIR = 'agent-skill';
|
|
76
|
+
/** Pre-1.5.3 location. Still recognised so existing installs stay manageable. */
|
|
77
|
+
const LEGACY_SKILL_SOURCE_DIR = 'skills';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Shape an owned symlink's destination must have: `…/pi-research/<dir>/pi-research`
|
|
81
|
+
* where `<dir>` is the current or the legacy source directory. Kept in sync with
|
|
82
|
+
* the duplicate in scripts/cleanup.cjs, which runs on preuninstall without this
|
|
83
|
+
* module available.
|
|
84
|
+
*/
|
|
85
|
+
const OWNED_SYMLINK_DEST = new RegExp(
|
|
86
|
+
`[/\\\\]${SKILL_NAME}[/\\\\](?:${SKILL_SOURCE_DIR}|${LEGACY_SKILL_SOURCE_DIR})[/\\\\]${SKILL_NAME}$`,
|
|
87
|
+
);
|
|
88
|
+
|
|
61
89
|
/**
|
|
62
90
|
* This package's version, used to stamp manifest entries so a stale COPY can be
|
|
63
91
|
* detected and refreshed. Read from the SKILL.md we are about to install rather
|
|
@@ -77,18 +105,17 @@ function readSkillVersion(sourceDir: string): string | null {
|
|
|
77
105
|
}
|
|
78
106
|
|
|
79
107
|
/**
|
|
80
|
-
* The coding agents the in-app installer (the /research-config TUI) targets
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* works. Every target is gated on the agent's config dir already existing (see
|
|
85
|
-
* skillInstallCandidates), so a tool that isn't set up is never offered and we
|
|
108
|
+
* The coding agents the in-app installer (the /research-config TUI) targets. Each
|
|
109
|
+
* loads skills from a home-directory `~/.<agent>/skills/` path, so a single global
|
|
110
|
+
* symlink works. Every target is gated on the agent's config dir already existing
|
|
111
|
+
* (see skillInstallCandidates), so a tool that isn't set up is never offered and we
|
|
86
112
|
* never create its home dir.
|
|
87
113
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
114
|
+
* `pi` (the host itself) and the speculative `~/.agents` convention are excluded:
|
|
115
|
+
* pi already exposes research as a native tool, and a skill in either root would
|
|
116
|
+
* only add a slower duplicate. Agents that expose no personal/global skills
|
|
117
|
+
* directory — only project-level ones — cannot be served by a global symlink and
|
|
118
|
+
* are likewise not targeted.
|
|
92
119
|
*/
|
|
93
120
|
export const SKILL_AGENT_TARGETS = ['claude', 'codex', 'openclaw'] as const;
|
|
94
121
|
|
|
@@ -166,7 +193,11 @@ function homeDir(opts?: { home?: string }): string {
|
|
|
166
193
|
* Locate the bundled skill source directory (the one containing SKILL.md).
|
|
167
194
|
* Robust across both the bundled CLI (dist/cli.mjs) and unbundled test runs:
|
|
168
195
|
* honors PI_RESEARCH_SKILL_DIR, then walks up from this module and from cwd
|
|
169
|
-
* looking for `
|
|
196
|
+
* looking for `agent-skill/pi-research/SKILL.md`.
|
|
197
|
+
*
|
|
198
|
+
* The legacy `skills/` location is still probed, after the current one, so a
|
|
199
|
+
* mixed tree (an older package still on disk beside a newer one) resolves rather
|
|
200
|
+
* than throwing. It is deliberately second: the current layout always wins.
|
|
170
201
|
*/
|
|
171
202
|
export function resolveSkillSourceDir(explicit?: string): string {
|
|
172
203
|
const candidates: string[] = [];
|
|
@@ -181,7 +212,8 @@ export function resolveSkillSourceDir(explicit?: string): string {
|
|
|
181
212
|
if (!start) continue;
|
|
182
213
|
let dir = start;
|
|
183
214
|
for (let i = 0; i < 8; i++) {
|
|
184
|
-
candidates.push(path.join(dir,
|
|
215
|
+
candidates.push(path.join(dir, SKILL_SOURCE_DIR, SKILL_NAME));
|
|
216
|
+
candidates.push(path.join(dir, LEGACY_SKILL_SOURCE_DIR, SKILL_NAME));
|
|
185
217
|
const parent = path.dirname(dir);
|
|
186
218
|
if (parent === dir) break;
|
|
187
219
|
dir = parent;
|
|
@@ -193,7 +225,7 @@ export function resolveSkillSourceDir(explicit?: string): string {
|
|
|
193
225
|
if (fs.existsSync(path.join(c, 'SKILL.md'))) return path.resolve(c);
|
|
194
226
|
} catch { /* next */ }
|
|
195
227
|
}
|
|
196
|
-
throw new Error('Could not locate the bundled research skill (
|
|
228
|
+
throw new Error('Could not locate the bundled research skill (agent-skill/pi-research/SKILL.md). Set PI_RESEARCH_SKILL_DIR to its path.');
|
|
197
229
|
}
|
|
198
230
|
|
|
199
231
|
export function getManifestPath(opts?: { home?: string }): string {
|
|
@@ -251,7 +283,7 @@ function upsertEntry(manifest: Manifest, entry: ManifestEntry): void {
|
|
|
251
283
|
// Ownership checks (never delete what isn't ours)
|
|
252
284
|
// ---------------------------------------------------------------------------
|
|
253
285
|
|
|
254
|
-
/** A symlink we own points at the skill source (our package's
|
|
286
|
+
/** A symlink we own points at the skill source (our package's agent-skill/pi-research). */
|
|
255
287
|
function isOwnedSymlink(targetPath: string, expectedSource?: string): boolean {
|
|
256
288
|
try {
|
|
257
289
|
const st = fs.lstatSync(targetPath);
|
|
@@ -259,11 +291,17 @@ function isOwnedSymlink(targetPath: string, expectedSource?: string): boolean {
|
|
|
259
291
|
const dest = path.resolve(path.dirname(targetPath), fs.readlinkSync(targetPath));
|
|
260
292
|
if (expectedSource && path.resolve(expectedSource) === dest) return true;
|
|
261
293
|
// Fallback: own it only if it points at OUR package layout, i.e. the dest
|
|
262
|
-
// ends in `pi-research
|
|
263
|
-
// immediate parent segment
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
|
|
294
|
+
// ends in `pi-research/<source-dir>/pi-research`. Requiring `pi-research` to be
|
|
295
|
+
// the immediate parent segment avoids the substring false positive where a
|
|
296
|
+
// foreign path like `/home/pi-researcher/.../skills/pi-research` (note:
|
|
297
|
+
// pi-researcher) would otherwise be misclassified as owned and deleted.
|
|
298
|
+
//
|
|
299
|
+
// BOTH the current and legacy source-dir names are accepted. Three of this
|
|
300
|
+
// function's four call sites pass no expectedSource and rely on this branch
|
|
301
|
+
// alone, so dropping the legacy name would strand every link created before
|
|
302
|
+
// 1.5.3: reconcile would stop re-pointing it, uninstall would refuse to
|
|
303
|
+
// remove it, and status would report it as a foreign skill.
|
|
304
|
+
return OWNED_SYMLINK_DEST.test(dest);
|
|
267
305
|
} catch { return false; }
|
|
268
306
|
}
|
|
269
307
|
|
|
@@ -19,45 +19,13 @@ import {
|
|
|
19
19
|
INTERNAL_NETWORK_PATTERNS,
|
|
20
20
|
type NativeHtmlToMarkdownModule,
|
|
21
21
|
} from './scraper-types.ts';
|
|
22
|
-
import { isCloudflareBlockError, isTaskTimeoutError, isPoolShutdownError } from '../infrastructure/browser/browser-error-utils.ts';
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* races. None of these is a pi-research bug: the URL is already accounted for as
|
|
29
|
-
* "not scraped" (scrape_results_total{outcome=total_failure}) and surfaced to the
|
|
30
|
-
* user that way, and no operator action follows. Callers use this to log such
|
|
31
|
-
* failures at debug instead of error — keeping the ERROR log (and, via
|
|
32
|
-
* logger.error's error-tracker re-track, the diagnostic error count) focused on
|
|
33
|
-
* genuine faults. The messages matched here are produced by validateContent
|
|
34
|
-
* (`Fetch blocked:` / `Fetch returned stub:`), scrapeWithFetch (`HTTP <code>`)
|
|
35
|
-
* and extractPdfToMarkdown (`Could not extract content from PDF`, `PDF too
|
|
36
|
-
* large`) — a malformed/encrypted/scanned-image-only PDF, or one that simply
|
|
37
|
-
* exceeds the deliberate 100MB policy cap, is routine on the open web and
|
|
38
|
-
* just as non-actionable as a stub page or a 404; the 25MB HTML policy caps
|
|
39
|
-
* (`HTML response too large` from the fetch layer, `Browser HTML too large`
|
|
40
|
-
* from the browser layer) are the same deliberate-cap class; the remaining
|
|
41
|
-
* cases delegate to the shared browser-layer predicates. Deliberately does NOT
|
|
42
|
-
* match 'PDF extraction unavailable' (a pdf-oxide-wasm native-module load
|
|
43
|
-
* failure) — that is an infrastructure fault affecting every URL, not a
|
|
44
|
-
* routine per-PDF outcome, and must still surface at ERROR.
|
|
24
|
+
* Re-exported from the browser error utilities, where it lives beside the three
|
|
25
|
+
* predicates it delegates to. Kept exported here because this is where every scrape
|
|
26
|
+
* caller already imports its error helpers from.
|
|
45
27
|
*/
|
|
46
|
-
export
|
|
47
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
48
|
-
return (
|
|
49
|
-
msg.startsWith('Fetch blocked:') || // bot-protection interstitial (Cloudflare, DDoS-Guard, …)
|
|
50
|
-
msg.startsWith('Fetch returned stub:') || // nav-only / near-empty page
|
|
51
|
-
msg.startsWith('Could not extract content from PDF') || // malformed/encrypted/scanned-only PDF
|
|
52
|
-
msg.startsWith('PDF too large') || // exceeds the deliberate 100MB policy cap
|
|
53
|
-
msg.startsWith('HTML response too large') || // fetch-layer 25MB policy cap — same class as the PDF cap
|
|
54
|
-
msg.startsWith('Browser HTML too large') || // browser-layer 25MB policy cap
|
|
55
|
-
/^HTTP \d{3}\b/.test(msg) || // remote 4xx/5xx status
|
|
56
|
-
isCloudflareBlockError(error) ||
|
|
57
|
-
isTaskTimeoutError(error) ||
|
|
58
|
-
isPoolShutdownError(error)
|
|
59
|
-
);
|
|
60
|
-
}
|
|
28
|
+
export { isBenignScrapeFailure } from '../infrastructure/browser/browser-error-utils.ts';
|
|
61
29
|
|
|
62
30
|
/**
|
|
63
31
|
* Get a random user agent
|
package/src/youtube/potoken.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* HTTP 200 with an EMPTY body (a silent failure). This module mints tokens with
|
|
7
7
|
* `bgutils-js`, running the BotGuard VM under a `jsdom` DOM.
|
|
8
8
|
*
|
|
9
|
-
* Hard-won implementation details (validated live against youtubei.js@
|
|
10
|
-
* bgutils-js@
|
|
9
|
+
* Hard-won implementation details (re-validated live against youtubei.js@18 /
|
|
10
|
+
* bgutils-js@4 / jsdom@29):
|
|
11
11
|
*
|
|
12
12
|
* 1. Cross-realm typed arrays. The BotGuard VM must run in the SAME JS realm as
|
|
13
13
|
* bgutils' `base64ToU8` (the Node realm). So the interpreter is eval'd via
|
|
@@ -171,9 +171,17 @@ async function doMint(
|
|
|
171
171
|
const started = Date.now();
|
|
172
172
|
|
|
173
173
|
// Lazy-load heavy deps — never imported at module load (keeps startup native-free).
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
// bgutils-js 4 dropped the barrel entry (package.json "exports" has no "."), so each
|
|
175
|
+
// piece comes from its own subpath: the challenge fetcher and VM client from
|
|
176
|
+
// /botguard, the minter from /webpo, the attestation URL/header helpers from /utils.
|
|
177
|
+
const [bgGuard, bgWebPo, bgUtils] = (await Promise.all([
|
|
178
|
+
import('bgutils-js/botguard'),
|
|
179
|
+
import('bgutils-js/webpo'),
|
|
180
|
+
import('bgutils-js/utils'),
|
|
181
|
+
])) as unknown as [BgGuardModule, BgWebPoModule, BgUtilsModule];
|
|
182
|
+
const { getChallenge, BotGuardClient } = bgGuard;
|
|
183
|
+
const { WebPoMinter } = bgWebPo;
|
|
184
|
+
const { buildURL, getHeaders } = bgUtils;
|
|
177
185
|
const { JSDOM } = await import('jsdom');
|
|
178
186
|
|
|
179
187
|
const dom = new JSDOM('<!DOCTYPE html><html lang="en"><body></body></html>', {
|
|
@@ -211,18 +219,18 @@ async function doMint(
|
|
|
211
219
|
|
|
212
220
|
bridge();
|
|
213
221
|
try {
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
// bgutils-js 4 replaced BG.Challenge.create(config) with a free getChallenge()
|
|
223
|
+
// whose config is narrower: no globalObj and no identifier (the content binding is
|
|
224
|
+
// supplied per-mint, which is what this module already did), and `fetch` renamed
|
|
225
|
+
// to `fetchFunction`.
|
|
226
|
+
const challenge = await getChallenge({
|
|
227
|
+
requestKey,
|
|
228
|
+
fetchFunction: ((input: unknown, init?: unknown) =>
|
|
216
229
|
fetchImpl(input as Parameters<typeof fetch>[0], {
|
|
217
230
|
...(init as RequestInit | undefined),
|
|
218
231
|
signal: (init as RequestInit | undefined)?.signal ?? opts.signal,
|
|
219
|
-
}),
|
|
220
|
-
|
|
221
|
-
identifier: identifiers[0]!,
|
|
222
|
-
requestKey,
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const challenge = await BG.Challenge.create(bgConfig);
|
|
232
|
+
})) as unknown as typeof fetch,
|
|
233
|
+
});
|
|
226
234
|
if (!challenge) throw new Error('BotGuard challenge unavailable');
|
|
227
235
|
|
|
228
236
|
const interpreter = challenge.interpreterJavascript?.privateDoNotAccessOrElseSafeScriptWrappedValue;
|
|
@@ -233,10 +241,16 @@ async function doMint(
|
|
|
233
241
|
// PoToken generation; trust rests on that TLS connection.
|
|
234
242
|
new Function(interpreter)();
|
|
235
243
|
|
|
236
|
-
|
|
244
|
+
if (!challenge.program || !challenge.globalName) {
|
|
245
|
+
throw new Error('BotGuard challenge missing program/globalName');
|
|
246
|
+
}
|
|
247
|
+
// `globalObj` was renamed `globalObject` in bgutils-js 4. Passing the old name
|
|
248
|
+
// leaves the VM without a global to attach to and the minter factory silently
|
|
249
|
+
// returns a non-function, so this rename is load-bearing, not cosmetic.
|
|
250
|
+
const botguard = await BotGuardClient.create({
|
|
237
251
|
program: challenge.program,
|
|
238
252
|
globalName: challenge.globalName,
|
|
239
|
-
|
|
253
|
+
globalObject: globalThis,
|
|
240
254
|
});
|
|
241
255
|
|
|
242
256
|
const webPoSignalOutput: unknown[] = [];
|
|
@@ -260,7 +274,7 @@ async function doMint(
|
|
|
260
274
|
throw new Error('Integrity token rejected by attestation server (likely a non-residential IP)');
|
|
261
275
|
}
|
|
262
276
|
|
|
263
|
-
const minter = await
|
|
277
|
+
const minter = await WebPoMinter.create({ integrityToken }, webPoSignalOutput);
|
|
264
278
|
|
|
265
279
|
const tokens = new Map<string, string>();
|
|
266
280
|
for (const id of identifiers) {
|
|
@@ -294,26 +308,27 @@ async function doMint(
|
|
|
294
308
|
// Minimal structural types for the loosely-typed bgutils-js surface we touch.
|
|
295
309
|
// ---------------------------------------------------------------------------
|
|
296
310
|
|
|
297
|
-
interface
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
requestKey: string;
|
|
311
|
+
interface BgChallenge {
|
|
312
|
+
interpreterJavascript?: { privateDoNotAccessOrElseSafeScriptWrappedValue?: string };
|
|
313
|
+
program?: string;
|
|
314
|
+
globalName?: string;
|
|
302
315
|
}
|
|
303
316
|
|
|
304
|
-
interface
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
};
|
|
317
|
+
interface BgGuardModule {
|
|
318
|
+
getChallenge(config: {
|
|
319
|
+
requestKey: string;
|
|
320
|
+
fetchFunction: typeof fetch;
|
|
321
|
+
interpreterHash?: string;
|
|
322
|
+
useYouTubeAPI?: boolean;
|
|
323
|
+
}): Promise<BgChallenge | undefined>;
|
|
312
324
|
BotGuardClient: {
|
|
313
|
-
create(opts: { program: string; globalName: string;
|
|
325
|
+
create(opts: { program: string; globalName: string; globalObject: unknown }): Promise<{
|
|
314
326
|
snapshot(args: { webPoSignalOutput: unknown[] }): Promise<string>;
|
|
315
327
|
}>;
|
|
316
328
|
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
interface BgWebPoModule {
|
|
317
332
|
WebPoMinter: {
|
|
318
333
|
create(
|
|
319
334
|
integrity: { integrityToken: string },
|
|
@@ -322,9 +337,7 @@ interface BgNamespace {
|
|
|
322
337
|
};
|
|
323
338
|
}
|
|
324
339
|
|
|
325
|
-
interface
|
|
326
|
-
BG?: BgNamespace;
|
|
327
|
-
default: BgNamespace;
|
|
340
|
+
interface BgUtilsModule {
|
|
328
341
|
buildURL(endpoint: string, useYouTubeAPI?: boolean): string;
|
|
329
342
|
getHeaders(): Record<string, string>;
|
|
330
343
|
}
|
|
File without changes
|