@reclaimprotocol/agent 0.1.25 → 0.1.27
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 +2 -0
- package/lib/live/tunnel.d.ts +7 -8
- package/lib/live/tunnel.js +67 -54
- package/lib/mcp/api/operations.gen.d.ts +4 -0
- package/lib/mcp/api/operations.gen.js +40 -0
- package/lib/mcp/instructions.js +2 -0
- package/lib/mcp/tools/agent/live-view.js +3 -20
- package/lib/old/client.d.ts +83 -0
- package/lib/old/client.js +46 -0
- package/lib/old/tools/build.js +2 -0
- package/lib/old/tools/session-logs.d.ts +3 -0
- package/lib/old/tools/session-logs.js +310 -0
- package/package.json +2 -2
- package/skill/SKILL.builder.md +16 -0
- package/skill/SKILL.old.md +28 -2
- package/skill/builder-mode.md +3 -3
- package/skill/old-mode.md +4 -2
- package/src/live/tunnel.ts +97 -65
- package/src/mcp/api/operations.gen.ts +44 -0
- package/src/mcp/instructions.ts +2 -0
- package/src/mcp/tools/agent/live-view.ts +3 -20
- package/src/old/client.ts +128 -0
- package/src/old/tools/build.ts +2 -0
- package/src/old/tools/session-logs.ts +395 -0
package/README.md
CHANGED
|
@@ -113,6 +113,8 @@ The MCP server targets one of two backends, selected by `USE_OLD_DEVTOOLS`:
|
|
|
113
113
|
- `reclaim_authenticate` — by default opens the dashboard in Chrome via CDP, waits for sign-in, reads the Firebase token from page storage, and closes the tab (or pass a `token` / `ethAddress` to skip the browser).
|
|
114
114
|
- `create_provider_version_from_capture` — registers a new provider, or adds a version to an existing one when given a `providerId`.
|
|
115
115
|
- `get_me_providers` — list the providers you own (the only provider-listing tool in this mode).
|
|
116
|
+
- `session_analytics_logs` — the milestone events for a verification session, which show how far it got.
|
|
117
|
+
- `session_logs` — the log entries the in-app SDK emitted during that session, each with its event type, log level, and logger name. Filter by event type, level, or substring, page with `limit` and `offset`, or set `saveTo` to write every matching entry to an NDJSON file. The backend searches only the last 3 days unless you pass `startTime` and `endTime`, and it deletes entries after 30 days.
|
|
116
118
|
- **Builder (`USE_OLD_DEVTOOLS=false`).** Registers the auto-generated HTTP-API tools plus the authentication/credential tools (`authenticate` device-pairing login, `issue_credentials` / `import_credentials` to set up the local eth proof-owner key, `resolve_owner_key`) and a verification-result helper (`get_verification_result`).
|
|
117
119
|
|
|
118
120
|
Relevant env vars: `RECLAIM_OLD_API_URL` (default `https://devapi.reclaimprotocol.org`), `RECLAIM_OLD_API_TOKEN` / `RECLAIM_OLD_ETH_UID` (headless identity), `RECLAIM_OLD_LOGIN_URL` (default `https://dev.reclaimprotocol.org`); builder mode uses `RECLAIM_API_URL` / `RECLAIM_API_TOKEN`.
|
package/lib/live/tunnel.d.ts
CHANGED
|
@@ -2,13 +2,6 @@ import { type CloudflaredBinary } from './cloudflared.ts';
|
|
|
2
2
|
export interface Tunnel {
|
|
3
3
|
/** The public HTTPS origin Cloudflare assigned. */
|
|
4
4
|
url: string;
|
|
5
|
-
/** Whether the tunnel was carrying traffic before {@link READY_TIMEOUT_MS}
|
|
6
|
-
* elapsed. False means it may still be propagating rather than broken — say
|
|
7
|
-
* so rather than treating the share as failed. */
|
|
8
|
-
ready: boolean;
|
|
9
|
-
/** cloudflared's own last words, for when `ready` is false and someone has
|
|
10
|
-
* to work out why. Absent when the tunnel came up normally. */
|
|
11
|
-
diagnostics?: string;
|
|
12
5
|
/** Terminate the tunnel. Safe to call more than once. */
|
|
13
6
|
stop: () => Promise<void>;
|
|
14
7
|
/** Which cloudflared ran, and whether we had to fetch it. */
|
|
@@ -21,6 +14,12 @@ export interface Tunnel {
|
|
|
21
14
|
* Rejects with an actionable message when `cloudflared` is not installed,
|
|
22
15
|
* exits early, or never reports a hostname.
|
|
23
16
|
*/
|
|
24
|
-
export declare function startTunnel(port: number, { autoInstall }?: {
|
|
17
|
+
export declare function startTunnel(port: number, { autoInstall, probePath, readyPollMs, readyTimeoutMs, }?: {
|
|
25
18
|
autoInstall?: boolean;
|
|
19
|
+
/** Public path that must answer before the tunnel is returned. */
|
|
20
|
+
probePath?: string;
|
|
21
|
+
/** Test-only timing override. */
|
|
22
|
+
readyPollMs?: number;
|
|
23
|
+
/** Test-only timing override. */
|
|
24
|
+
readyTimeoutMs?: number;
|
|
26
25
|
}): Promise<Tunnel>;
|
package/lib/live/tunnel.js
CHANGED
|
@@ -25,56 +25,42 @@ import { resolveCloudflared } from "./cloudflared.js";
|
|
|
25
25
|
const URL_TIMEOUT_MS = 30000;
|
|
26
26
|
/** After the hostname is printed, Cloudflare needs a moment before the edge
|
|
27
27
|
* routes to it — measured at ~3s. Wait for it so the caller never hands out a
|
|
28
|
-
* URL that errors
|
|
28
|
+
* URL that errors. */
|
|
29
29
|
const READY_TIMEOUT_MS = 20000;
|
|
30
30
|
const READY_POLL_MS = 1000;
|
|
31
|
+
const STOP_TIMEOUT_MS = 5000;
|
|
31
32
|
/** `https://<something>.trycloudflare.com`, as printed on cloudflared's log
|
|
32
33
|
* stream. Anchored to the scheme so a hostname inside prose can't match. */
|
|
33
34
|
const QUICK_TUNNEL_URL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
|
|
34
|
-
/**
|
|
35
|
-
* cloudflared says this once per edge connection it establishes, and until it
|
|
36
|
-
* does, the hostname resolves but routes nowhere.
|
|
37
|
-
*
|
|
38
|
-
* This is the signal to trust, because it is LOCAL. Asking whether we can fetch
|
|
39
|
-
* the public URL ourselves conflates two questions — is the tunnel up, and does
|
|
40
|
-
* this machine have egress to Cloudflare's edge — and reports "not ready" when
|
|
41
|
-
* only the second fails. Observed: a tunnel printed its hostname and registered
|
|
42
|
-
* nothing for 12s, which is indistinguishable from a blocked fetch unless you
|
|
43
|
-
* read cloudflared's own account of it.
|
|
44
|
-
*/
|
|
45
|
-
const REGISTERED = /Registered tunnel connection|connIndex=/i;
|
|
46
35
|
/** Tail of cloudflared's log kept for error messages. */
|
|
47
36
|
const LOG_TAIL_BYTES = 4096;
|
|
48
37
|
/**
|
|
49
38
|
* Wait until the tunnel is actually carrying traffic.
|
|
50
39
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* - the public origin answered. ANY HTTP response counts, including a 404:
|
|
55
|
-
* the live-view server 404s unknown paths, so a status code at all proves
|
|
56
|
-
* the hop is live. Kept as a second opinion in case a cloudflared release
|
|
57
|
-
* rewords its log.
|
|
40
|
+
* A cloudflared "Registered tunnel connection" log is not enough: Cloudflare
|
|
41
|
+
* can report one while the assigned hostname still returns DNS NXDOMAIN. The
|
|
42
|
+
* public viewer itself must return a successful response.
|
|
58
43
|
*/
|
|
59
|
-
async function waitUntilRouted(url,
|
|
60
|
-
const deadline = Date.now() +
|
|
44
|
+
async function waitUntilRouted(url, { isRunning, pollMs, timeoutMs, }) {
|
|
45
|
+
const deadline = Date.now() + timeoutMs;
|
|
61
46
|
while (Date.now() < deadline) {
|
|
62
|
-
if (
|
|
63
|
-
return
|
|
47
|
+
if (!isRunning()) {
|
|
48
|
+
return false;
|
|
64
49
|
}
|
|
65
50
|
try {
|
|
66
|
-
await fetch(url, {
|
|
51
|
+
const response = await fetch(url, {
|
|
67
52
|
method: 'GET',
|
|
68
53
|
redirect: 'manual',
|
|
69
|
-
signal: AbortSignal.timeout(
|
|
54
|
+
signal: AbortSignal.timeout(pollMs * 2),
|
|
70
55
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
await new Promise((r) => setTimeout(r, READY_POLL_MS));
|
|
56
|
+
if (response.ok) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
75
59
|
}
|
|
60
|
+
catch { }
|
|
61
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
76
62
|
}
|
|
77
|
-
return
|
|
63
|
+
return false;
|
|
78
64
|
}
|
|
79
65
|
/**
|
|
80
66
|
* Open a quick tunnel to `http://127.0.0.1:<port>` and resolve once
|
|
@@ -83,7 +69,7 @@ async function waitUntilRouted(url, isRegistered) {
|
|
|
83
69
|
* Rejects with an actionable message when `cloudflared` is not installed,
|
|
84
70
|
* exits early, or never reports a hostname.
|
|
85
71
|
*/
|
|
86
|
-
export async function startTunnel(port, { autoInstall = true } = {}) {
|
|
72
|
+
export async function startTunnel(port, { autoInstall = true, probePath = '/', readyPollMs = READY_POLL_MS, readyTimeoutMs = READY_TIMEOUT_MS, } = {}) {
|
|
87
73
|
// Resolves an existing cloudflared, or fetches one on first use. Throws
|
|
88
74
|
// with an actionable message when neither is possible.
|
|
89
75
|
const binary = await resolveCloudflared({ autoInstall });
|
|
@@ -92,20 +78,12 @@ export async function startTunnel(port, { autoInstall = true } = {}) {
|
|
|
92
78
|
'--no-autoupdate',
|
|
93
79
|
'--url', `http://127.0.0.1:${port}`,
|
|
94
80
|
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
81
|
+
const exited = new Promise((resolve) => {
|
|
82
|
+
child.once('exit', () => resolve());
|
|
83
|
+
});
|
|
84
|
+
let stopPromise;
|
|
85
|
+
const stop = () => stopPromise ?? (stopPromise = stopChild());
|
|
100
86
|
return new Promise((resolve, reject) => {
|
|
101
|
-
const timer = setTimeout(() => {
|
|
102
|
-
void stop();
|
|
103
|
-
reject(new Error('cloudflared did not report a tunnel hostname within '
|
|
104
|
-
+ `${URL_TIMEOUT_MS / 1000}s. Check network egress to Cloudflare, `
|
|
105
|
-
+ 'or run `cloudflared tunnel --url http://127.0.0.1:'
|
|
106
|
-
+ `${port}` + '` by hand to see its output.'));
|
|
107
|
-
}, URL_TIMEOUT_MS);
|
|
108
|
-
let registered = false;
|
|
109
87
|
let tail = '';
|
|
110
88
|
let settled = false;
|
|
111
89
|
const finish = (fn) => {
|
|
@@ -116,27 +94,42 @@ export async function startTunnel(port, { autoInstall = true } = {}) {
|
|
|
116
94
|
clearTimeout(timer);
|
|
117
95
|
fn();
|
|
118
96
|
};
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
finish(() => {
|
|
99
|
+
void stop().then(() => reject(new Error('cloudflared did not report a tunnel hostname within '
|
|
100
|
+
+ `${URL_TIMEOUT_MS / 1000}s. Check network egress to `
|
|
101
|
+
+ 'Cloudflare, or run `cloudflared tunnel --url '
|
|
102
|
+
+ `http://127.0.0.1:${port}` + '` by hand to see its output.')));
|
|
103
|
+
});
|
|
104
|
+
}, URL_TIMEOUT_MS);
|
|
119
105
|
// cloudflared logs the assigned hostname to stderr, not stdout. Watch
|
|
120
106
|
// both so a future version that switches streams still works.
|
|
121
107
|
const onChunk = (chunk) => {
|
|
122
108
|
const text = chunk.toString();
|
|
123
|
-
// Keep watching after the hostname
|
|
124
|
-
//
|
|
109
|
+
// Keep watching after the hostname so a readiness failure can include
|
|
110
|
+
// cloudflared's account of what happened.
|
|
125
111
|
tail = (tail + text).slice(-LOG_TAIL_BYTES);
|
|
126
|
-
if (REGISTERED.test(text)) {
|
|
127
|
-
registered = true;
|
|
128
|
-
}
|
|
129
112
|
const match = QUICK_TUNNEL_URL.exec(text);
|
|
130
113
|
if (match) {
|
|
131
114
|
const url = match[0];
|
|
132
115
|
finish(() => {
|
|
133
|
-
|
|
116
|
+
const probeUrl = new URL(probePath, `${url}/`).toString();
|
|
117
|
+
void waitUntilRouted(probeUrl, {
|
|
118
|
+
isRunning: () => child.exitCode === null
|
|
119
|
+
&& child.signalCode === null,
|
|
120
|
+
pollMs: readyPollMs,
|
|
121
|
+
timeoutMs: readyTimeoutMs,
|
|
122
|
+
}).then(async (ready) => {
|
|
134
123
|
if (ready) {
|
|
135
|
-
resolve({ url,
|
|
124
|
+
resolve({ url, stop, binary });
|
|
136
125
|
return;
|
|
137
126
|
}
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
await stop();
|
|
128
|
+
LOGGER.warn({ url, tail }, 'tunnel public URL never became reachable');
|
|
129
|
+
reject(new Error('cloudflared created a hostname, but the public viewer did '
|
|
130
|
+
+ `not become reachable within ${readyTimeoutMs / 1000}s. `
|
|
131
|
+
+ 'Retry share_browser_view. cloudflared output:\n'
|
|
132
|
+
+ tail));
|
|
140
133
|
});
|
|
141
134
|
});
|
|
142
135
|
}
|
|
@@ -157,4 +150,24 @@ export async function startTunnel(port, { autoInstall = true } = {}) {
|
|
|
157
150
|
+ 'hostname.')));
|
|
158
151
|
});
|
|
159
152
|
});
|
|
153
|
+
async function stopChild() {
|
|
154
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
let timeout;
|
|
158
|
+
const timedOut = new Promise((resolve) => {
|
|
159
|
+
timeout = setTimeout(resolve, STOP_TIMEOUT_MS);
|
|
160
|
+
});
|
|
161
|
+
child.kill('SIGTERM');
|
|
162
|
+
await Promise.race([
|
|
163
|
+
exited,
|
|
164
|
+
timedOut,
|
|
165
|
+
]);
|
|
166
|
+
clearTimeout(timeout);
|
|
167
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
child.kill('SIGKILL');
|
|
171
|
+
await exited;
|
|
172
|
+
}
|
|
160
173
|
}
|
|
@@ -111,6 +111,10 @@ export declare const MCP_TOOLS: {
|
|
|
111
111
|
description: string;
|
|
112
112
|
inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
|
|
113
113
|
};
|
|
114
|
+
ImportDevtoolsProvider: {
|
|
115
|
+
description: string;
|
|
116
|
+
inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
|
|
117
|
+
};
|
|
114
118
|
IssueOrgToken: {
|
|
115
119
|
description: string;
|
|
116
120
|
inputSchema: import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
|
|
@@ -1812,6 +1812,46 @@ export const MCP_TOOLS = {
|
|
|
1812
1812
|
"additionalProperties": false
|
|
1813
1813
|
}),
|
|
1814
1814
|
},
|
|
1815
|
+
"ImportDevtoolsProvider": {
|
|
1816
|
+
description: "Import a provider from devtools\n\nImport the latest version of a public provider from the legacy devtools\nservice. Use this when moving an existing provider to Builder instead\nof recreating it.\n\nThe import preserves the legacy provider UUID. The server reads the\ncurrent configuration from\n`https://api.reclaimprotocol.org/api/providers/{providerId}` and\ntranslates it to the Builder schema.\n\nIf the provider already exists in the seeded anonymous catalog, the\nimport assigns that record to the target organization and adds the\nimported version idempotently. A provider owned by another Builder\norganization returns `409`.\n",
|
|
1817
|
+
inputSchema: toZod({
|
|
1818
|
+
"type": "object",
|
|
1819
|
+
"properties": {
|
|
1820
|
+
"params": {
|
|
1821
|
+
"type": "object",
|
|
1822
|
+
"properties": {}
|
|
1823
|
+
},
|
|
1824
|
+
"query": {
|
|
1825
|
+
"type": "object",
|
|
1826
|
+
"properties": {}
|
|
1827
|
+
},
|
|
1828
|
+
"body": {
|
|
1829
|
+
"type": "object",
|
|
1830
|
+
"additionalProperties": false,
|
|
1831
|
+
"required": [
|
|
1832
|
+
"orgId",
|
|
1833
|
+
"providerId"
|
|
1834
|
+
],
|
|
1835
|
+
"properties": {
|
|
1836
|
+
"orgId": {
|
|
1837
|
+
"type": "string",
|
|
1838
|
+
"format": "uuid"
|
|
1839
|
+
},
|
|
1840
|
+
"providerId": {
|
|
1841
|
+
"type": "string",
|
|
1842
|
+
"format": "uuid"
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
},
|
|
1847
|
+
"required": [
|
|
1848
|
+
"params",
|
|
1849
|
+
"query",
|
|
1850
|
+
"body"
|
|
1851
|
+
],
|
|
1852
|
+
"additionalProperties": false
|
|
1853
|
+
}),
|
|
1854
|
+
},
|
|
1815
1855
|
"IssueOrgToken": {
|
|
1816
1856
|
description: "Issue a new organization API key\n\nIssues a fresh per-organization API key (`rorg_…`). The caller must be an active **OWNER** of the organization. An org may hold several keys; issuing a new one does **not** invalidate existing keys (revoke those explicitly). The plaintext key is returned **only once** in this response (the server stores only its HMAC); treat it like a password.",
|
|
1817
1857
|
inputSchema: toZod({
|
package/lib/mcp/instructions.js
CHANGED
|
@@ -61,6 +61,8 @@ const SHARED_TOPIC_FILES = {
|
|
|
61
61
|
const TOPIC_SUMMARIES = {
|
|
62
62
|
'overview': 'Terminology, your stance, and the whole loop at a glance.',
|
|
63
63
|
'auth': 'Signing in to the backend, and which token authorizes what.',
|
|
64
|
+
'devtools-migration': 'Importing a public legacy provider with the same '
|
|
65
|
+
+ 'UUID.',
|
|
64
66
|
'browser': 'Choosing a browser source (local Chrome, the Reclaim runtime in '
|
|
65
67
|
+ 'Docker, a remote Popcorn browser, or a custom CDP endpoint), '
|
|
66
68
|
+ 'geolocation, live view, and launch troubleshooting.',
|
|
@@ -83,7 +83,9 @@ export function liveViewTools(attachRef, shareRef) {
|
|
|
83
83
|
const viewerPort = attached.viewerPort;
|
|
84
84
|
assert(viewerPort, badRequest('This browser reports no live-view port. Re-attach with mode '
|
|
85
85
|
+ '"container".'));
|
|
86
|
-
const tunnel = await startTunnel(viewerPort
|
|
86
|
+
const tunnel = await startTunnel(viewerPort, {
|
|
87
|
+
probePath: '/liveview.html?magnify=1',
|
|
88
|
+
});
|
|
87
89
|
// The path is the runtime's own viewer page; `magnify=1` is what makes
|
|
88
90
|
// it lay out for the device that opens it.
|
|
89
91
|
const url = `${tunnel.url}/liveview.html?magnify=1`;
|
|
@@ -97,30 +99,11 @@ export function liveViewTools(attachRef, shareRef) {
|
|
|
97
99
|
...(share.tunnel.binary.source === 'downloaded'
|
|
98
100
|
? { cloudflaredInstalled: true }
|
|
99
101
|
: {}),
|
|
100
|
-
...(share.tunnel.ready
|
|
101
|
-
? {}
|
|
102
|
-
: {
|
|
103
|
-
propagating: true,
|
|
104
|
-
// cloudflared's own last words. Without them, "the URL does
|
|
105
|
-
// not connect" is unactionable: the tunnel, the local server
|
|
106
|
-
// and this machine's egress all look identical from outside.
|
|
107
|
-
...(share.tunnel.diagnostics
|
|
108
|
-
? { cloudflaredLog: share.tunnel.diagnostics }
|
|
109
|
-
: {}),
|
|
110
|
-
}),
|
|
111
102
|
_notes: [
|
|
112
103
|
...(share.tunnel.binary.source === 'downloaded'
|
|
113
104
|
? ['cloudflared was not installed, so it was downloaded once '
|
|
114
105
|
+ 'to ~/.reclaim/bin. Mention that; later shares reuse it.']
|
|
115
106
|
: []),
|
|
116
|
-
...(share.tunnel.ready
|
|
117
|
-
? []
|
|
118
|
-
: ['The tunnel never confirmed a connection to Cloudflare, so '
|
|
119
|
-
+ 'the URL may not resolve yet. Give it ~10s and reload '
|
|
120
|
-
+ 'first. If it still fails, say so and offer to retry '
|
|
121
|
-
+ '(stop_browser_view then share_browser_view) — a quick '
|
|
122
|
-
+ 'tunnel does sometimes fail to register. '
|
|
123
|
-
+ '`cloudflaredLog` has its output if it keeps happening.']),
|
|
124
107
|
'Relay `url` to the developer verbatim, on its own line.',
|
|
125
108
|
'The URL is a credential: it needs no login, and anyone with it '
|
|
126
109
|
+ 'can see this browser and act in it as the signed-in user.',
|
package/lib/old/client.d.ts
CHANGED
|
@@ -94,6 +94,70 @@ export interface LinkNonce {
|
|
|
94
94
|
nonce: string;
|
|
95
95
|
expiresInSeconds: number;
|
|
96
96
|
}
|
|
97
|
+
/** Log-level filter accepted by the devtools backend's session-logs route.
|
|
98
|
+
* It validates against this exact list and rejects anything else with a 400,
|
|
99
|
+
* including the comma-separated lists the log-stream service itself would
|
|
100
|
+
* accept. Each value is hierarchical at the log-stream end: `fine` means
|
|
101
|
+
* FINE+CONFIG+INFO+WARNING+SEVERE, `info` means INFO+WARNING+SEVERE, and so
|
|
102
|
+
* on. `finer`/`finest` (the PII tiers) are not reachable through this route
|
|
103
|
+
* at all. */
|
|
104
|
+
export type SessionLogLevel = 'fine' | 'config' | 'info' | 'warning' | 'severe' | 'unknown';
|
|
105
|
+
/** Filters for {@link ReclaimOldClient.getSessionLogs}. Every one is optional
|
|
106
|
+
* and forwarded verbatim; the session id itself is the route's path param, so
|
|
107
|
+
* `providerId`/`deviceId` narrow WITHIN one session rather than searching
|
|
108
|
+
* across sessions. */
|
|
109
|
+
export interface SessionLogsQuery {
|
|
110
|
+
deviceId?: string;
|
|
111
|
+
source?: string;
|
|
112
|
+
/** The client-side logger NAME (log-stream's `log_type` column, the SDK's
|
|
113
|
+
* `LogEntry.type`) — not the event type. */
|
|
114
|
+
logType?: string;
|
|
115
|
+
logLevel?: SessionLogLevel;
|
|
116
|
+
providerId?: string;
|
|
117
|
+
/** ISO-8601. Omitting BOTH bounds makes the log-stream service default to
|
|
118
|
+
* the last 3 days. */
|
|
119
|
+
startTime?: string;
|
|
120
|
+
endTime?: string;
|
|
121
|
+
/** Substring match against the log line (`LIKE %…%`). */
|
|
122
|
+
logLine?: string;
|
|
123
|
+
/** Exact `LogEventType` name, for example `REQUEST_MATCHED`. */
|
|
124
|
+
eventType?: string;
|
|
125
|
+
/** Capped at 1000 by the log-stream service. */
|
|
126
|
+
limit?: number;
|
|
127
|
+
offset?: number;
|
|
128
|
+
includeCount?: boolean;
|
|
129
|
+
}
|
|
130
|
+
/** One raw log line, in the log-stream service's snake_case ClickHouse shape,
|
|
131
|
+
* passed through unchanged by the devtools backend. `event_type` is the SDK's
|
|
132
|
+
* `LogEventType` milestone marker (`REQUEST_MATCHED`,
|
|
133
|
+
* `CLAIM_CREATION_STARTED`, `PROOF_GENERATED`, …); it is `''` on the majority
|
|
134
|
+
* of lines, which carry no event. */
|
|
135
|
+
export interface SessionLogRow {
|
|
136
|
+
timestamp: string;
|
|
137
|
+
request_id: string;
|
|
138
|
+
session_id: string;
|
|
139
|
+
device_id: string;
|
|
140
|
+
source: string;
|
|
141
|
+
app_id: string;
|
|
142
|
+
provider_id: string;
|
|
143
|
+
log_type: string;
|
|
144
|
+
log_level: string;
|
|
145
|
+
log_line: string;
|
|
146
|
+
event_type?: string;
|
|
147
|
+
metadata?: string;
|
|
148
|
+
}
|
|
149
|
+
/** `GET /api/logger/:sessionId`'s `data` envelope. The backend queries every
|
|
150
|
+
* region that holds the session, concatenates the pages, sorts them
|
|
151
|
+
* newest-first and re-slices to `limit`, so `data` is one page and
|
|
152
|
+
* `totalCount` is the match count across all of them. */
|
|
153
|
+
export interface SessionLogsPage {
|
|
154
|
+
data: SessionLogRow[];
|
|
155
|
+
count: number;
|
|
156
|
+
totalCount?: number;
|
|
157
|
+
/** Regions whose log-stream query failed — the page is INCOMPLETE when
|
|
158
|
+
* this is present. */
|
|
159
|
+
failedRegions?: string[];
|
|
160
|
+
}
|
|
97
161
|
/**
|
|
98
162
|
* Minimal REST client for the OLD devtools Express backend (base `/api`).
|
|
99
163
|
* Deliberately NOT a clone of `@reclaimprotocol/client`'s operationId
|
|
@@ -133,6 +197,25 @@ export declare class ReclaimOldClient {
|
|
|
133
197
|
* `#request`). Attaches the caller identity when one is set; queried
|
|
134
198
|
* anonymously otherwise. */
|
|
135
199
|
getSessionAnalyticsLogs(sessionId: string): Promise<unknown>;
|
|
200
|
+
/**
|
|
201
|
+
* Raw log LINES for a verification session — the SDK's own diagnostic
|
|
202
|
+
* logging, the thing the dashboard's session "logs" tab renders, and a
|
|
203
|
+
* strictly richer signal than `getSessionAnalyticsLogs`'s milestone events.
|
|
204
|
+
*
|
|
205
|
+
* This is the devtools backend (`GET /api/logger/:sessionId`), NOT the
|
|
206
|
+
* analytics-logs host — hence `#request`, and hence the same identity and
|
|
207
|
+
* ownership rules as every other authed call here: the session's app must
|
|
208
|
+
* be one you created (admins excepted), or the route 404s.
|
|
209
|
+
*
|
|
210
|
+
* The backend fans the query out to the per-region log-stream services,
|
|
211
|
+
* which read the ClickHouse `logs` table. Two defaults applied down there
|
|
212
|
+
* bite hard if you don't override them:
|
|
213
|
+
* - no `startTime`/`endTime` → only the last 3 days are searched;
|
|
214
|
+
* - no `logLevel` → INFO and above only, so every FINE/CONFIG line is
|
|
215
|
+
* invisible.
|
|
216
|
+
* Entries are dropped entirely after 30 days (table TTL).
|
|
217
|
+
*/
|
|
218
|
+
getSessionLogs(sessionId: string, query?: SessionLogsQuery): Promise<SessionLogsPage>;
|
|
136
219
|
/** Mint a brand-new, unassociated (sandboxed) app — no auth required. Link
|
|
137
220
|
* it to an account afterwards with `getLinkNonce` + `linkAppToAccount`, or
|
|
138
221
|
* use it as-is within the sandbox session limit. */
|
package/lib/old/client.js
CHANGED
|
@@ -89,6 +89,52 @@ export class ReclaimOldClient {
|
|
|
89
89
|
}
|
|
90
90
|
return json;
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Raw log LINES for a verification session — the SDK's own diagnostic
|
|
94
|
+
* logging, the thing the dashboard's session "logs" tab renders, and a
|
|
95
|
+
* strictly richer signal than `getSessionAnalyticsLogs`'s milestone events.
|
|
96
|
+
*
|
|
97
|
+
* This is the devtools backend (`GET /api/logger/:sessionId`), NOT the
|
|
98
|
+
* analytics-logs host — hence `#request`, and hence the same identity and
|
|
99
|
+
* ownership rules as every other authed call here: the session's app must
|
|
100
|
+
* be one you created (admins excepted), or the route 404s.
|
|
101
|
+
*
|
|
102
|
+
* The backend fans the query out to the per-region log-stream services,
|
|
103
|
+
* which read the ClickHouse `logs` table. Two defaults applied down there
|
|
104
|
+
* bite hard if you don't override them:
|
|
105
|
+
* - no `startTime`/`endTime` → only the last 3 days are searched;
|
|
106
|
+
* - no `logLevel` → INFO and above only, so every FINE/CONFIG line is
|
|
107
|
+
* invisible.
|
|
108
|
+
* Entries are dropped entirely after 30 days (table TTL).
|
|
109
|
+
*/
|
|
110
|
+
async getSessionLogs(sessionId, query = {}) {
|
|
111
|
+
const res = await __classPrivateFieldGet(this, _ReclaimOldClient_instances, "m", _ReclaimOldClient_request).call(this, 'GET', `/logger/${encodeURIComponent(sessionId)}`, {
|
|
112
|
+
auth: true,
|
|
113
|
+
query: {
|
|
114
|
+
deviceId: query.deviceId,
|
|
115
|
+
source: query.source,
|
|
116
|
+
logType: query.logType,
|
|
117
|
+
logLevel: query.logLevel,
|
|
118
|
+
providerId: query.providerId,
|
|
119
|
+
startTime: query.startTime,
|
|
120
|
+
endTime: query.endTime,
|
|
121
|
+
logLine: query.logLine,
|
|
122
|
+
eventType: query.eventType,
|
|
123
|
+
limit: query.limit,
|
|
124
|
+
offset: query.offset,
|
|
125
|
+
includeCount: query.includeCount === undefined
|
|
126
|
+
? undefined
|
|
127
|
+
: String(query.includeCount),
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const page = res.data;
|
|
131
|
+
return {
|
|
132
|
+
data: page?.data ?? [],
|
|
133
|
+
count: page?.count ?? page?.data?.length ?? 0,
|
|
134
|
+
totalCount: page?.totalCount,
|
|
135
|
+
failedRegions: page?.failedRegions,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
92
138
|
/** Mint a brand-new, unassociated (sandboxed) app — no auth required. Link
|
|
93
139
|
* it to an account afterwards with `getLinkNonce` + `linkAppToAccount`, or
|
|
94
140
|
* use it as-is within the sandbox session limit. */
|
package/lib/old/tools/build.js
CHANGED
|
@@ -8,6 +8,7 @@ import { getMeProvidersTool } from "./me-providers.js";
|
|
|
8
8
|
import { createVersionFromCaptureTool } from "./publish.js";
|
|
9
9
|
import { scaffoldDemoServerTool } from "./scaffold-demo-server.js";
|
|
10
10
|
import { sessionAnalyticsLogsTool } from "./session-analytics-logs.js";
|
|
11
|
+
import { sessionLogsTool } from "./session-logs.js";
|
|
11
12
|
import { updateProviderMetadataTool } from "./update-provider-metadata.js";
|
|
12
13
|
import { verifySdkSetupTool } from "./verify-sdk-setup.js";
|
|
13
14
|
/**
|
|
@@ -32,6 +33,7 @@ export function buildOldBackendTools(client, sdkClient, attachRef) {
|
|
|
32
33
|
getProviderInfoTool(sdkClient),
|
|
33
34
|
updateProviderMetadataTool(client),
|
|
34
35
|
sessionAnalyticsLogsTool(client),
|
|
36
|
+
sessionLogsTool(client),
|
|
35
37
|
issueAppCredentialsTool(client),
|
|
36
38
|
linkAppTool(client),
|
|
37
39
|
checkAppStatusTool(client),
|