agentlinkops 0.5.0 → 0.5.1
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.mcp.json +8 -0
- package/CHANGELOG.md +15 -1
- package/README.md +2 -1
- package/cli/citation.js +19 -1
- package/cli/client.js +1 -1
- package/cli/doctor.js +19 -0
- package/package.json +9 -1
- package/src/citations/browser/engine.js +196 -0
- package/src/citations/browser/gen/core/providers/_shared/google.js +4 -0
- package/src/citations/browser/gen/core/providers/_shared/rawSourcesDom.js +15 -0
- package/src/citations/browser/gen/core/providers/_shared/resetProviderPage.js +22 -0
- package/src/citations/browser/gen/core/providers/_shared/sourceUtils.js +8 -0
- package/src/citations/browser/gen/core/providers/ai-overview/index.js +70 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/expand.js +59 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/extractSources.js +60 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/pageLifecycle.js +18 -0
- package/src/citations/browser/gen/core/providers/ai-overview/lib/session.js +62 -0
- package/src/citations/browser/gen/core/providers/chatgpt/index.js +28 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/dismissAuthModal.js +39 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/extractSources.js +66 -0
- package/src/citations/browser/gen/core/providers/chatgpt/lib/pageLifecycle.js +11 -0
- package/src/citations/browser/gen/core/providers/claude/index.js +16 -0
- package/src/citations/browser/gen/core/providers/claude/lib/extractSources.js +54 -0
- package/src/citations/browser/gen/core/providers/claude/lib/pageLifecycle.js +9 -0
- package/src/citations/browser/gen/core/providers/gemini/index.js +40 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/extractSources.js +65 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/pageLifecycle.js +14 -0
- package/src/citations/browser/gen/core/providers/gemini/lib/session.js +45 -0
- package/src/citations/browser/gen/core/providers/index.js +15 -0
- package/src/citations/browser/gen/core/providers/perplexity/index.js +31 -0
- package/src/citations/browser/gen/core/providers/perplexity/lib/extractSources.js +84 -0
- package/src/citations/browser/gen/core/providers/perplexity/lib/navigation.js +38 -0
- package/src/citations/browser/gen/core/providers/types.js +0 -0
- package/src/citations/browser/gen/lib/browser/camoufox.js +587 -0
- package/src/citations/browser/gen/lib/browser/display.js +147 -0
- package/src/citations/browser/gen/lib/browser/domOps.js +339 -0
- package/src/citations/browser/gen/lib/browser/humanBehavior.js +121 -0
- package/src/citations/browser/gen/lib/browser/navigate.js +46 -0
- package/src/citations/browser/gen/lib/browser/playwrightCompat.js +321 -0
- package/src/citations/browser/gen/lib/browser/proxy/forwarder.js +251 -0
- package/src/citations/browser/gen/lib/browser/runtimeTypes.js +0 -0
- package/src/citations/browser/gen/lib/extraction/sourceUtils.js +71 -0
- package/src/citations/browser/gen/lib/input/editor/assertNotBlocked.js +54 -0
- package/src/citations/browser/gen/lib/input/editor/clearInput.js +52 -0
- package/src/citations/browser/gen/lib/input/editor/findEditor.js +54 -0
- package/src/citations/browser/gen/lib/input/editor/findSendButton.js +21 -0
- package/src/citations/browser/gen/lib/input/editor/promptInput.js +117 -0
- package/src/citations/browser/gen/lib/input/markdown/converter.js +101 -0
- package/src/citations/browser/gen/lib/input/markdown/toMarkdown.js +14 -0
- package/src/citations/browser/gen/lib/input/response/detectBotPage.js +13 -0
- package/src/citations/browser/gen/lib/input/response/getText.js +10 -0
- package/src/citations/browser/gen/lib/input/response/isGenerating.js +59 -0
- package/src/citations/browser/gen/lib/input/response/waitForFinish.js +105 -0
- package/src/citations/browser/gen/lib/input/sources/findButton.js +41 -0
- package/src/citations/browser/gen/lib/input/sources/openPanel.js +11 -0
- package/src/citations/browser/gen/utils/agent-constants.js +87 -0
- package/src/citations/browser/shims/errors.js +26 -0
- package/src/citations/browser/shims/types.js +6 -0
- package/src/citations/browser/shims/utils.js +32 -0
- package/src/citations/contract.js +9 -2
- package/src/citations/runner.js +21 -5
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const PROVIDER_NO_OUTPUT_TIMEOUT_MS = {
|
|
2
|
+
chatgpt: 9e4,
|
|
3
|
+
perplexity: 45e3,
|
|
4
|
+
gemini: 45e3,
|
|
5
|
+
claude: 6e4,
|
|
6
|
+
"ai-overview": 45e3
|
|
7
|
+
};
|
|
8
|
+
const PROVIDER_FORCE_EXIT_STABLE_MS = {
|
|
9
|
+
chatgpt: 45e3,
|
|
10
|
+
perplexity: 3e4,
|
|
11
|
+
gemini: 45e3,
|
|
12
|
+
claude: 45e3,
|
|
13
|
+
"ai-overview": 3e4
|
|
14
|
+
};
|
|
15
|
+
const PROVIDER_EDITOR_SELECTORS = {
|
|
16
|
+
chatgpt: [
|
|
17
|
+
"#prompt-textarea",
|
|
18
|
+
'div#prompt-textarea[contenteditable="true"][role="textbox"]',
|
|
19
|
+
'div.ProseMirror[contenteditable="true"][role="textbox"]',
|
|
20
|
+
'div[contenteditable="true"][role="textbox"][aria-multiline="true"][aria-label="Chat with ChatGPT"]'
|
|
21
|
+
],
|
|
22
|
+
perplexity: [
|
|
23
|
+
"#ask-input",
|
|
24
|
+
'div#ask-input[contenteditable="true"][role="textbox"]',
|
|
25
|
+
'div[role="textbox"][data-lexical-editor="true"]',
|
|
26
|
+
'div[contenteditable="true"][role="textbox"][data-lexical-editor="true"]'
|
|
27
|
+
],
|
|
28
|
+
gemini: [
|
|
29
|
+
'div[aria-label="Enter a prompt for Gemini"]',
|
|
30
|
+
'rich-textarea [contenteditable="true"][role="textbox"]',
|
|
31
|
+
'div[contenteditable="true"][role="textbox"][aria-multiline="true"]'
|
|
32
|
+
],
|
|
33
|
+
claude: [
|
|
34
|
+
'[data-testid="chat-input"]',
|
|
35
|
+
'div[data-testid="chat-input"][contenteditable="true"][role="textbox"]',
|
|
36
|
+
'[data-testid="chat-input"][aria-multiline="true"]'
|
|
37
|
+
],
|
|
38
|
+
"ai-overview": ['textarea[name="q"][role="combobox"]', 'textarea[role="combobox"][aria-label="Search"]']
|
|
39
|
+
};
|
|
40
|
+
const PROVIDER_SUBMIT_BTN_SELECTORS = {
|
|
41
|
+
chatgpt: ['button[data-testid="send-button"]'],
|
|
42
|
+
perplexity: ['button[aria-label*="Submit"]'],
|
|
43
|
+
gemini: ['button[aria-label*="Send"]'],
|
|
44
|
+
claude: ['button[aria-label*="Send"]'],
|
|
45
|
+
"ai-overview": []
|
|
46
|
+
};
|
|
47
|
+
const PROVIDER_MODEL_RESPONSE_SELECTORS = {
|
|
48
|
+
chatgpt: [
|
|
49
|
+
'[data-message-author-role="assistant"]',
|
|
50
|
+
'[data-testid^="conversation-turn"][data-turn="assistant"]'
|
|
51
|
+
],
|
|
52
|
+
perplexity: [
|
|
53
|
+
'div[id^="markdown-content-"]',
|
|
54
|
+
'[id^="markdown-content-"] .prose'
|
|
55
|
+
],
|
|
56
|
+
gemini: ["message-content .markdown"],
|
|
57
|
+
claude: [
|
|
58
|
+
'[data-is-streaming="false"] .standard-markdown',
|
|
59
|
+
".standard-markdown"
|
|
60
|
+
],
|
|
61
|
+
"ai-overview": [
|
|
62
|
+
'[data-container-id="main-col"]'
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
const PROVIDER_RESPONSE_GENERATION_SELECTORS = {
|
|
66
|
+
chatgpt: ['button[data-testid="stop-button"]', 'button[aria-label*="stop" i]'],
|
|
67
|
+
perplexity: ['button[aria-label*="stop" i]'],
|
|
68
|
+
gemini: ['button[aria-label*="stop" i]'],
|
|
69
|
+
claude: ['button[aria-label*="stop" i]'],
|
|
70
|
+
"ai-overview": []
|
|
71
|
+
};
|
|
72
|
+
const RETRYABLE_ERRORS = [
|
|
73
|
+
"ERR_SSL_PROTOCOL_ERROR",
|
|
74
|
+
"ERR_CONNECTION",
|
|
75
|
+
"ERR_TIMED_OUT",
|
|
76
|
+
"ERR_PROXY_CONNECTION_FAILED",
|
|
77
|
+
"Timeout"
|
|
78
|
+
];
|
|
79
|
+
export {
|
|
80
|
+
PROVIDER_EDITOR_SELECTORS,
|
|
81
|
+
PROVIDER_FORCE_EXIT_STABLE_MS,
|
|
82
|
+
PROVIDER_MODEL_RESPONSE_SELECTORS,
|
|
83
|
+
PROVIDER_NO_OUTPUT_TIMEOUT_MS,
|
|
84
|
+
PROVIDER_RESPONSE_GENERATION_SELECTORS,
|
|
85
|
+
PROVIDER_SUBMIT_BTN_SELECTORS,
|
|
86
|
+
RETRYABLE_ERRORS
|
|
87
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Local shims for the @oneglanse/* runtime imports in the vendored browser
|
|
2
|
+
// layer. Upstream, these live in workspace packages we did not vendor; the
|
|
3
|
+
// behavior below is the minimal honest equivalent. The logger writes to
|
|
4
|
+
// stderr so a --json stdout reader never sees framework chatter.
|
|
5
|
+
export class ExternalServiceError extends Error {
|
|
6
|
+
constructor(service, message, status, meta, cause) {
|
|
7
|
+
super(`${service}: ${message}`);
|
|
8
|
+
this.name = 'ExternalServiceError';
|
|
9
|
+
this.service = service;
|
|
10
|
+
this.status = status ?? null;
|
|
11
|
+
this.meta = meta ?? null;
|
|
12
|
+
if (cause) this.cause = cause;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class NotFoundError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'NotFoundError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function toErrorMessage(error) {
|
|
24
|
+
if (error instanceof Error) return error.message;
|
|
25
|
+
return String(error);
|
|
26
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Shim for @oneglanse/utils runtime exports. The selector/timeout constants
|
|
2
|
+
// are re-exported from the vendored agent-constants module unchanged; logger,
|
|
3
|
+
// getDomain and getFaviconUrls are minimal equivalents.
|
|
4
|
+
export {
|
|
5
|
+
PROVIDER_NO_OUTPUT_TIMEOUT_MS,
|
|
6
|
+
PROVIDER_FORCE_EXIT_STABLE_MS,
|
|
7
|
+
PROVIDER_EDITOR_SELECTORS,
|
|
8
|
+
PROVIDER_SUBMIT_BTN_SELECTORS,
|
|
9
|
+
PROVIDER_MODEL_RESPONSE_SELECTORS,
|
|
10
|
+
PROVIDER_RESPONSE_GENERATION_SELECTORS,
|
|
11
|
+
RETRYABLE_ERRORS,
|
|
12
|
+
} from '../gen/utils/agent-constants.js';
|
|
13
|
+
|
|
14
|
+
const envQuiet = () => Boolean(process.env.AGENTLINKOPS_BROWSER_QUIET);
|
|
15
|
+
|
|
16
|
+
export const logger = {
|
|
17
|
+
log: (...args) => { if (!envQuiet()) console.error('[browser]', ...args); },
|
|
18
|
+
warn: (...args) => { if (!envQuiet()) console.error('[browser:warn]', ...args); },
|
|
19
|
+
error: (...args) => { console.error('[browser:error]', ...args); },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function getDomain(url) {
|
|
23
|
+
try { return new URL(url).hostname.replace(/^www\./, ''); }
|
|
24
|
+
catch { return null; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getFaviconUrls(url) {
|
|
28
|
+
try {
|
|
29
|
+
const u = new URL(url);
|
|
30
|
+
return [`https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32`];
|
|
31
|
+
} catch { return []; }
|
|
32
|
+
}
|
|
@@ -74,7 +74,10 @@ export const citationPromptSchema = z.object({
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
export const citationEngineSpecSchema = z.object({
|
|
77
|
-
|
|
77
|
+
// API engines ('mock', 'perplexity') or browser surfaces ('chatgpt',
|
|
78
|
+
// 'perplexity', 'gemini', 'claude', 'ai-overview' with via:'browser').
|
|
79
|
+
engine: z.string().min(2).max(40),
|
|
80
|
+
via: z.enum(['api', 'browser']).default('api'),
|
|
78
81
|
model: z.string().min(1).max(80).optional(),
|
|
79
82
|
});
|
|
80
83
|
|
|
@@ -95,7 +98,8 @@ export const citationPanelSchema = z.object({
|
|
|
95
98
|
// The engine identity names HOW a surface was reached, because the same engine two
|
|
96
99
|
// ways is two different measurements and is never averaged (limelitgeo/open's rule).
|
|
97
100
|
export function engineIdentity(spec) {
|
|
98
|
-
const
|
|
101
|
+
const provider = spec.via === 'browser' ? 'web-own-browser' : spec.engine === 'mock' ? 'builtin' : 'api';
|
|
102
|
+
const parts = [spec.engine, provider];
|
|
99
103
|
if (spec.model) parts.push(spec.model);
|
|
100
104
|
return parts.join(':');
|
|
101
105
|
}
|
|
@@ -117,6 +121,9 @@ export const evidenceEnvelopeSchema = z.object({
|
|
|
117
121
|
// evidence snapshots distinct content-addressed objects rather than silent
|
|
118
122
|
// duplicates of each other.
|
|
119
123
|
run_index: z.number().int().nonnegative(),
|
|
124
|
+
// Sibling screenshot of the answer surface, same directory as this envelope;
|
|
125
|
+
// present when the engine captures pixels (browser surfaces always do).
|
|
126
|
+
screenshot_file: z.string().max(120).optional(),
|
|
120
127
|
prompt: z.string().min(1),
|
|
121
128
|
engine_identity: z.string().min(1),
|
|
122
129
|
provider_model_version: z.string().min(1),
|
package/src/citations/runner.js
CHANGED
|
@@ -94,6 +94,9 @@ export async function runEpoch(panelInput, options = {}) {
|
|
|
94
94
|
run = await cell.engine.run({ prompt: cell.prompt.text, runIndex });
|
|
95
95
|
} catch (cause) {
|
|
96
96
|
if (cause instanceof EngineError && cause.retriable) {
|
|
97
|
+
// An unknown is honest, but never silent: the reason travels to
|
|
98
|
+
// stderr so a run of unknowns names its wall.
|
|
99
|
+
if (options.err) options.err(`unknown observation (${cell.id}): ${cause.message}`);
|
|
97
100
|
outcomesByCell.get(cell.id).push('unknown');
|
|
98
101
|
continue;
|
|
99
102
|
}
|
|
@@ -101,7 +104,7 @@ export async function runEpoch(panelInput, options = {}) {
|
|
|
101
104
|
}
|
|
102
105
|
spent += run.costEstimateUsd ?? 0;
|
|
103
106
|
|
|
104
|
-
const envelope =
|
|
107
|
+
const envelope = {
|
|
105
108
|
schema_version: CITATIONS_VERSION,
|
|
106
109
|
epoch_id: epochId, cell_id: cell.id, run_index: runIndex, prompt: cell.prompt.text,
|
|
107
110
|
engine_identity: cell.identity,
|
|
@@ -110,10 +113,17 @@ export async function runEpoch(panelInput, options = {}) {
|
|
|
110
113
|
usage: run.usage ?? { input_tokens: 0, output_tokens: 0 },
|
|
111
114
|
cost_estimate_usd: run.costEstimateUsd ?? 0,
|
|
112
115
|
at: nowIso(),
|
|
113
|
-
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
};
|
|
117
|
+
const digest = sha256(JSON.stringify(envelope));
|
|
118
|
+
// The screenshot is a sibling of the envelope: same content-addressed
|
|
119
|
+
// directory, named by the envelope digest it belongs to. An observation
|
|
120
|
+
// of a rendered surface carries the pixels of that surface.
|
|
121
|
+
if (run.screenshotPng) {
|
|
122
|
+
const shotName = `${digest}.screenshot.png`;
|
|
123
|
+
await writeFile(join(evidenceRoot, shotName), run.screenshotPng);
|
|
124
|
+
envelope.screenshot_file = shotName;
|
|
125
|
+
}
|
|
126
|
+
await writeFile(join(evidenceRoot, `${digest}.json`), JSON.stringify(evidenceEnvelopeSchema.parse(envelope)), 'utf8');
|
|
117
127
|
|
|
118
128
|
const tiered = classifyOutcome(cell.target, run, options.verifyFetch ?? null);
|
|
119
129
|
outcomesByCell.get(cell.id).push(tiered.outcome);
|
|
@@ -147,5 +157,11 @@ export async function runEpoch(panelInput, options = {}) {
|
|
|
147
157
|
await writeFile(join(dir, CITATION_FILES.observations), observationRows.map((r) => JSON.stringify(r)).join('\n') + (observationRows.length ? '\n' : ''), { flag: 'a' });
|
|
148
158
|
await writeFile(join(dir, CITATION_FILES.epochs), epochRows.map((r) => JSON.stringify(r)).join('\n') + (epochRows.length ? '\n' : ''), { flag: 'a' });
|
|
149
159
|
|
|
160
|
+
// Browser engines hold a browser and maybe an Xvfb display for the whole
|
|
161
|
+
// epoch; release both once the tallies are written.
|
|
162
|
+
for (const engine of new Set(cells.map((c) => c.engine))) {
|
|
163
|
+
try { await engine.close?.(); } catch { /* cleanup is best-effort */ }
|
|
164
|
+
}
|
|
165
|
+
|
|
150
166
|
return { epochId, cells: epochRows, spentEstimateUsd: Number(spent.toFixed(6)), aborted, samples, maxUsd };
|
|
151
167
|
}
|