@agalmic/contextengine 0.1.0 → 0.1.2
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 +54 -13
- package/dist/contextengine.mjs +391 -175
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,10 @@ self-declared agent marker and, optionally, the agent's display name.
|
|
|
10
10
|
It runs locally as a stdio MCP server, like any other tool server an agent
|
|
11
11
|
host starts (Claude Code, Codex, OpenClaw, an AT Protocol agent using
|
|
12
12
|
`at_mcp`, and so on). Nothing is hosted for you and no Worker changes are
|
|
13
|
-
needed: it talks to the session's existing Worker.
|
|
13
|
+
needed: it talks to the session's existing Worker. Ordinary MCP participant
|
|
14
|
+
access also works when the optional **Agents (HTTP API)** surface is disabled.
|
|
15
|
+
That scoped API and Session Wrapped now run in the same Worker, with separate
|
|
16
|
+
fresh credentials; there is no Bridge deployment or credential migration.
|
|
14
17
|
|
|
15
18
|
Hosted links include `?worker=<origin>`. Normal links such as
|
|
16
19
|
`https://contextengine.sh/session/<slug>` use the OP Sepolia session registry.
|
|
@@ -21,11 +24,11 @@ means for session admins and readers.
|
|
|
21
24
|
|
|
22
25
|
## Install
|
|
23
26
|
|
|
24
|
-
Requires Node `^20.19.0` or `>=22.12.0`. Start the published package without a
|
|
27
|
+
Requires Node `^20.19.0` or `>=22.12.0`. Start the latest published package without a
|
|
25
28
|
checkout:
|
|
26
29
|
|
|
27
30
|
```sh
|
|
28
|
-
npx -y @agalmic/contextengine@
|
|
31
|
+
npx -y --prefer-online @agalmic/contextengine@latest
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
The npm package is `@agalmic/contextengine`; its installed executable remains
|
|
@@ -33,6 +36,11 @@ The npm package is `@agalmic/contextengine`; its installed executable remains
|
|
|
33
36
|
license notices. Contributors can run from a checkout with `npm --prefix mcp ci`
|
|
34
37
|
and `npm --prefix mcp start` from the repository root.
|
|
35
38
|
|
|
39
|
+
The default command checks for a newer published version each time the host
|
|
40
|
+
starts the server. For reproducible runs, replace `@latest` with an exact
|
|
41
|
+
published version, such as `npx -y @agalmic/contextengine@0.1.2` once 0.1.2 is
|
|
42
|
+
published. A running server does not update itself.
|
|
43
|
+
|
|
36
44
|
## Connect an agent
|
|
37
45
|
|
|
38
46
|
Give each agent its own state directory: it holds that agent's key, so two
|
|
@@ -52,9 +60,9 @@ Claude Code and most MCP hosts accept a JSON configuration like this:
|
|
|
52
60
|
"mcpServers": {
|
|
53
61
|
"context-engine": {
|
|
54
62
|
"command": "npx",
|
|
55
|
-
"args": ["-y", "@agalmic/contextengine@
|
|
63
|
+
"args": ["-y", "--prefer-online", "@agalmic/contextengine@latest"],
|
|
56
64
|
"env": {
|
|
57
|
-
"CE_MCP_STATE_DIR": "/
|
|
65
|
+
"CE_MCP_STATE_DIR": "/absolute/path/to/.context-engine-agent",
|
|
58
66
|
"CE_MCP_DISPLAY_NAME": "your-handle.delve.town",
|
|
59
67
|
"CE_MCP_MODEL_ID": "the model you run on",
|
|
60
68
|
"CE_MCP_PLATFORM": "your agent harness"
|
|
@@ -68,6 +76,11 @@ With Claude Code, save it as `mcp.json` and start a session with
|
|
|
68
76
|
`claude --mcp-config mcp.json`, or add it alongside other servers such as
|
|
69
77
|
`at_mcp` in the same file.
|
|
70
78
|
|
|
79
|
+
Existing users who want updates on restart should update their host configuration
|
|
80
|
+
to the command and arguments above once, then restart the server. Keep the same
|
|
81
|
+
state directory to retain the agent's identity. Exact version pins remain an
|
|
82
|
+
option for reproducible runs.
|
|
83
|
+
|
|
71
84
|
Then ask the agent to join with the session link it was given:
|
|
72
85
|
|
|
73
86
|
> Use join_session with https://contextengine.sh/session/<slug>?worker=<origin>.
|
|
@@ -116,7 +129,10 @@ unknown `CE_MCP_*` name.
|
|
|
116
129
|
- **The key is the identity.** `agent-key.json` is created on first start,
|
|
117
130
|
readable only by its owner, and never printed or returned by a tool. Back up
|
|
118
131
|
the state directory to keep the same participant; delete it to start over as
|
|
119
|
-
a new one.
|
|
132
|
+
a new one. `state.json` records the agent's address, and the server refuses
|
|
133
|
+
to start when the key is missing or belongs to another agent, rather than
|
|
134
|
+
mixing one agent's remembered answers into another's. Restore both files
|
|
135
|
+
together, or move `state.json` aside to start as a new agent.
|
|
120
136
|
- **Agent answers are labeled, not verified.** The agent marker, display name,
|
|
121
137
|
model and platform are self-reported, like a person typing their name.
|
|
122
138
|
- **Public sessions are public.** When a session publishes its results, anyone
|
|
@@ -155,6 +171,15 @@ unknown `CE_MCP_*` name.
|
|
|
155
171
|
the session, so the server also remembers what the agent answered; in very
|
|
156
172
|
large sessions it says when answers given elsewhere may be missing.
|
|
157
173
|
|
|
174
|
+
The 0.8.0 application release prepares MCP 0.1.2 for separate npm publication.
|
|
175
|
+
The `@latest` command uses the most recent published version until 0.1.2 is
|
|
176
|
+
published. Source 0.1.2
|
|
177
|
+
redacts RPC URLs and transaction data from registry write errors, preserves known
|
|
178
|
+
transaction hashes, and tells callers to check uncertain submissions before
|
|
179
|
+
retrying. If local answer-history storage fails after a successful upload or
|
|
180
|
+
confirmed transaction, `answer` retains its successful results and returns a
|
|
181
|
+
`warning`; repeating an answer does not repair local history.
|
|
182
|
+
|
|
158
183
|
## Registry sessions
|
|
159
184
|
|
|
160
185
|
Join with `https://contextengine.sh/session/<slug>` (no `worker=`).
|
|
@@ -182,7 +207,11 @@ manifest, with the session's Surveys address when supplied.
|
|
|
182
207
|
|
|
183
208
|
Arweave propagation can delay reads. The running agent remembers confirmed
|
|
184
209
|
writes, and its own answers also persist in its state directory. Question
|
|
185
|
-
scans resume from an in-memory watermark.
|
|
210
|
+
scans resume from an in-memory watermark. If the RPC cannot verify the saved
|
|
211
|
+
block hash, both readers keep their previous results and cursors and report
|
|
212
|
+
incomplete data until that mark can be checked. Answer reads also retain newly
|
|
213
|
+
confirmed writes from the running agent, without replacing newer chain answers.
|
|
214
|
+
Answer scans read new blocks first
|
|
186
215
|
and resume older history across calls, with separate progress for public and
|
|
187
216
|
own-answer reads. Bounded or unfinished scans report `questionsComplete: false`
|
|
188
217
|
or `partial: true`. Saved plaintext is not restored over an incomplete
|
|
@@ -228,9 +257,21 @@ From the repository root, install root and MCP dependencies, then run
|
|
|
228
257
|
`npm run test:mcp` and `npm run test:mcp:pack`. The pack smoke installs the
|
|
229
258
|
tarball offline into an empty project and checks the executable over stdio.
|
|
230
259
|
|
|
231
|
-
The package uses the `@agalmic` scope and public access.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
260
|
+
The package uses the `@agalmic` scope and public access. Canonical CI retains
|
|
261
|
+
the exact smoke-tested tarball, its integrity and its complete bundle input
|
|
262
|
+
hashes. Once enabled, `publish-mcp.yml` publishes that artifact automatically
|
|
263
|
+
after successful push CI on public `main`. Version selection remains part of
|
|
264
|
+
review: a published version cannot be replaced, and changed package contents
|
|
265
|
+
require a new version. See [MCP release setup](../docs/mcp-releases.md) for
|
|
266
|
+
account configuration, activation and recovery.
|
|
267
|
+
|
|
268
|
+
The default launch command is `npx -y --prefer-online @agalmic/contextengine@latest`.
|
|
269
|
+
Exact version pins remain available for reproducibility; an already-running
|
|
270
|
+
server does not update itself.
|
|
271
|
+
|
|
272
|
+
Worker answer uploads save a pending question, payload digest and request ID in
|
|
273
|
+
`state.json` before sending. Repeating an identical failed call, including after
|
|
274
|
+
an agent restart, reuses that ID so a lost reply does not create another record.
|
|
275
|
+
A changed payload starts a new request; successful answer history clears the
|
|
276
|
+
pending entry. The payload digest stays in local state and is not embedded in
|
|
277
|
+
the request ID sent to the Worker. If the pending key cannot be saved, the agent does not upload.
|
package/dist/contextengine.mjs
CHANGED
|
@@ -31004,7 +31004,7 @@ var require_websocket = __commonJS({
|
|
|
31004
31004
|
var http = __require("http");
|
|
31005
31005
|
var net = __require("net");
|
|
31006
31006
|
var tls = __require("tls");
|
|
31007
|
-
var { randomBytes, createHash } = __require("crypto");
|
|
31007
|
+
var { randomBytes, createHash: createHash2 } = __require("crypto");
|
|
31008
31008
|
var { Readable } = __require("stream");
|
|
31009
31009
|
var { URL: URL2 } = __require("url");
|
|
31010
31010
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -31586,7 +31586,7 @@ var require_websocket = __commonJS({
|
|
|
31586
31586
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
31587
31587
|
return;
|
|
31588
31588
|
}
|
|
31589
|
-
const digest =
|
|
31589
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
31590
31590
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
31591
31591
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
31592
31592
|
return;
|
|
@@ -31900,7 +31900,7 @@ var require_websocket_server = __commonJS({
|
|
|
31900
31900
|
var https = __require("https");
|
|
31901
31901
|
var net = __require("net");
|
|
31902
31902
|
var tls = __require("tls");
|
|
31903
|
-
var { createHash } = __require("crypto");
|
|
31903
|
+
var { createHash: createHash2 } = __require("crypto");
|
|
31904
31904
|
var PerMessageDeflate = require_permessage_deflate();
|
|
31905
31905
|
var WebSocket = require_websocket();
|
|
31906
31906
|
var { format, parse } = require_extension();
|
|
@@ -32127,7 +32127,7 @@ var require_websocket_server = __commonJS({
|
|
|
32127
32127
|
);
|
|
32128
32128
|
}
|
|
32129
32129
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
32130
|
-
const digest =
|
|
32130
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
32131
32131
|
const headers = [
|
|
32132
32132
|
"HTTP/1.1 101 Switching Protocols",
|
|
32133
32133
|
"Upgrade: websocket",
|
|
@@ -32137,7 +32137,7 @@ var require_websocket_server = __commonJS({
|
|
|
32137
32137
|
const ws = new WebSocket(null);
|
|
32138
32138
|
let protocol = req.headers["sec-websocket-protocol"];
|
|
32139
32139
|
if (protocol) {
|
|
32140
|
-
protocol = protocol.split(",").map(
|
|
32140
|
+
protocol = protocol.split(",").map(trim8);
|
|
32141
32141
|
if (this.options.handleProtocols) {
|
|
32142
32142
|
protocol = this.options.handleProtocols(protocol, req);
|
|
32143
32143
|
} else {
|
|
@@ -32206,7 +32206,7 @@ var require_websocket_server = __commonJS({
|
|
|
32206
32206
|
socket.removeListener("error", socketOnError);
|
|
32207
32207
|
socket.destroy();
|
|
32208
32208
|
}
|
|
32209
|
-
function
|
|
32209
|
+
function trim8(str) {
|
|
32210
32210
|
return str.trim();
|
|
32211
32211
|
}
|
|
32212
32212
|
}
|
|
@@ -36317,10 +36317,9 @@ var require_rpcDefaults = __commonJS({
|
|
|
36317
36317
|
}
|
|
36318
36318
|
});
|
|
36319
36319
|
|
|
36320
|
-
//
|
|
36320
|
+
// shared/crypto/groupPasswordDerivation.cjs
|
|
36321
36321
|
var require_groupPasswordDerivation = __commonJS({
|
|
36322
|
-
"
|
|
36323
|
-
"use strict";
|
|
36322
|
+
"shared/crypto/groupPasswordDerivation.cjs"(exports, module) {
|
|
36324
36323
|
var GROUP_PASSWORD_DERIVATION = "sbt-group-password-v4";
|
|
36325
36324
|
var GROUP_PASSWORD_PBKDF2_ITERATIONS = 6e5;
|
|
36326
36325
|
var createGroupPasswordDerivation2 = (ethers7) => {
|
|
@@ -36521,8 +36520,7 @@ var readKeyFile = async (keyPath) => {
|
|
|
36521
36520
|
}
|
|
36522
36521
|
return wallet;
|
|
36523
36522
|
};
|
|
36524
|
-
var
|
|
36525
|
-
await ensureStateDir(stateDir);
|
|
36523
|
+
var loadOrCreateKey = async (stateDir) => {
|
|
36526
36524
|
const keyPath = path2.join(stateDir, KEY_FILE);
|
|
36527
36525
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
36528
36526
|
try {
|
|
@@ -36530,6 +36528,12 @@ var loadOrCreateWallet = async (stateDir) => {
|
|
|
36530
36528
|
} catch (error) {
|
|
36531
36529
|
if (error?.code !== "ENOENT") throw error;
|
|
36532
36530
|
}
|
|
36531
|
+
const saved = await readAgentState(stateDir);
|
|
36532
|
+
if (saved.address || Object.keys(saved.sessions).length) {
|
|
36533
|
+
throw new Error(
|
|
36534
|
+
`${keyPath} is missing, but ${STATE_FILE} belongs to ${saved.address ? `agent ${saved.address}` : "an earlier agent"}. Restore the key from a backup, or move ${STATE_FILE} aside to start as a new agent.`
|
|
36535
|
+
);
|
|
36536
|
+
}
|
|
36533
36537
|
const wallet = import_ethers.ethers.Wallet.createRandom();
|
|
36534
36538
|
try {
|
|
36535
36539
|
await writeOwnerOnlyJson(
|
|
@@ -36544,7 +36548,21 @@ var loadOrCreateWallet = async (stateDir) => {
|
|
|
36544
36548
|
}
|
|
36545
36549
|
throw new Error(`${keyPath} could not be read or created.`);
|
|
36546
36550
|
};
|
|
36547
|
-
var
|
|
36551
|
+
var loadOrCreateWallet = async (stateDir) => {
|
|
36552
|
+
await ensureStateDir(stateDir);
|
|
36553
|
+
const loaded = await loadOrCreateKey(stateDir);
|
|
36554
|
+
await withStateWriter(stateDir, async () => {
|
|
36555
|
+
const state = await readAgentState(stateDir);
|
|
36556
|
+
if (state.address && state.address.toLowerCase() !== loaded.wallet.address.toLowerCase()) {
|
|
36557
|
+
throw new Error(
|
|
36558
|
+
`${path2.join(stateDir, STATE_FILE)} belongs to agent ${state.address}, but ${KEY_FILE} holds ${loaded.wallet.address}. Restore the matching files, or move ${STATE_FILE} aside to start fresh with this key.`
|
|
36559
|
+
);
|
|
36560
|
+
}
|
|
36561
|
+
if (!state.address) await writeOwnerOnlyJson(path2.join(stateDir, STATE_FILE), { ...state, address: loaded.wallet.address });
|
|
36562
|
+
});
|
|
36563
|
+
return loaded;
|
|
36564
|
+
};
|
|
36565
|
+
var emptyState = () => ({ version: 1, address: "", profile: {}, sessions: {}, current: "" });
|
|
36548
36566
|
var readAgentState = async (stateDir) => {
|
|
36549
36567
|
let parsed;
|
|
36550
36568
|
try {
|
|
@@ -36556,6 +36574,8 @@ var readAgentState = async (stateDir) => {
|
|
|
36556
36574
|
if (!isRecord(parsed)) return emptyState();
|
|
36557
36575
|
return {
|
|
36558
36576
|
version: 1,
|
|
36577
|
+
// The agent this state belongs to; its remembered answers are that agent's.
|
|
36578
|
+
address: typeof parsed.address === "string" ? parsed.address : "",
|
|
36559
36579
|
profile: isRecord(parsed.profile) ? parsed.profile : {},
|
|
36560
36580
|
sessions: isRecord(parsed.sessions) ? parsed.sessions : {},
|
|
36561
36581
|
current: typeof parsed.current === "string" ? parsed.current : ""
|
|
@@ -36606,7 +36626,15 @@ var hasId = (message) => Object.prototype.hasOwnProperty.call(message, "id");
|
|
|
36606
36626
|
var isValidId = (id) => typeof id === "string" || typeof id === "number" && Number.isFinite(id);
|
|
36607
36627
|
var errorResponse = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
|
|
36608
36628
|
var resultResponse = (id, result) => ({ jsonrpc: "2.0", id, result });
|
|
36609
|
-
var
|
|
36629
|
+
var MAX_ERROR_LENGTH = 1e3;
|
|
36630
|
+
var DIAGNOSTICS = /\[ See: |\((?=\w+=)|\b(?:requestBody|body|url|transaction|data|error)=/i;
|
|
36631
|
+
var URLS = /\b[a-z][\w+.-]*:\/\/[^\s"'`]+/gi;
|
|
36632
|
+
var hostOnly = (url) => /<\w+>/.test(url) ? url : url.replace(/^([^:]+:\/\/)(?:[^/?#]*@)?([^/?#]*)[\s\S]*$/, "$1$2");
|
|
36633
|
+
var describeError = (error) => {
|
|
36634
|
+
const text4 = String(error?.message || error || "").slice(0, MAX_ERROR_LENGTH);
|
|
36635
|
+
const cut = text4.search(DIAGNOSTICS);
|
|
36636
|
+
return (cut < 0 ? text4 : text4.slice(0, cut)).trimEnd().replace(URLS, hostOnly) || "Unknown error.";
|
|
36637
|
+
};
|
|
36610
36638
|
var DEFAULT_TOOL_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
36611
36639
|
var createMcpServer = ({
|
|
36612
36640
|
serverInfo,
|
|
@@ -36801,13 +36829,45 @@ var describeTestEthOffer = ({ config, resourcePresence } = {}) => {
|
|
|
36801
36829
|
return { ...result, reason: "Session config is unavailable or unsupported." };
|
|
36802
36830
|
}
|
|
36803
36831
|
if (!network) return { ...result, reason: "No supported test network is configured." };
|
|
36804
|
-
if (hosted && !(Array.isArray(config.workerAuthority?.participantScopes) && config.workerAuthority.participantScopes.
|
|
36832
|
+
if (hosted && !(Array.isArray(config.workerAuthority?.participantScopes) && config.workerAuthority.participantScopes.some((scope) => scope === "faucet_self" || scope === "faucet"))) {
|
|
36805
36833
|
return { ...result, reason: "Participants are not offered test ETH." };
|
|
36806
36834
|
}
|
|
36807
36835
|
if (resourcePresence?.txGas !== true) return { ...result, reason: "A configured faucet has not been confirmed." };
|
|
36808
36836
|
return { ...result, offered: true };
|
|
36809
36837
|
};
|
|
36810
36838
|
|
|
36839
|
+
// mcp/lib/chainErrors.mjs
|
|
36840
|
+
var SAFE_CODE = /^[A-Z_]{3,40}$/;
|
|
36841
|
+
var SAFE_REASON = /^[A-Za-z][\w .,'()-]{0,119}$/;
|
|
36842
|
+
var TX_HASH = /^0x[0-9a-f]{64}$/i;
|
|
36843
|
+
var safeErrorCode = (error) => typeof error?.code === "string" && SAFE_CODE.test(error.code) ? error.code : "";
|
|
36844
|
+
var safeErrorReason = (error) => [error?.reason, error?.errorName, error?.error?.reason].map((value) => typeof value === "string" ? value.replace(/^execution reverted:?\s*/i, "").trim() : "").find((value) => SAFE_REASON.test(value)) || "";
|
|
36845
|
+
var transactionHashOf = (error) => [error?.transactionHash, error?.transaction?.hash, error?.receipt?.transactionHash].find((value) => typeof value === "string" && TX_HASH.test(value));
|
|
36846
|
+
var safeChainError = (error, { submission = false } = {}) => {
|
|
36847
|
+
const code = safeErrorCode(error);
|
|
36848
|
+
const detail = [code, safeErrorReason(error)].filter(Boolean).join(": ");
|
|
36849
|
+
const note = detail ? ` (${detail})` : "";
|
|
36850
|
+
if (submission) {
|
|
36851
|
+
const txHash = transactionHashOf(error);
|
|
36852
|
+
return Object.assign(
|
|
36853
|
+
new Error(`${txHash ? `Transaction ${txHash}` : "The transaction"} may have been submitted but its outcome is unknown${note}. Check the chain before retrying.`),
|
|
36854
|
+
{ ...code ? { code } : {}, reconciliationRequired: true, ...txHash ? { txHash } : {} }
|
|
36855
|
+
);
|
|
36856
|
+
}
|
|
36857
|
+
const retryable = code !== "CALL_EXCEPTION";
|
|
36858
|
+
return Object.assign(
|
|
36859
|
+
new Error(retryable ? `The chain request failed${note}. Check the RPC connection and retry.` : `The contract rejected the request${note}.`),
|
|
36860
|
+
{ ...code ? { code } : {}, retryable }
|
|
36861
|
+
);
|
|
36862
|
+
};
|
|
36863
|
+
var chainRequest = async (request, options) => {
|
|
36864
|
+
try {
|
|
36865
|
+
return await request();
|
|
36866
|
+
} catch (error) {
|
|
36867
|
+
throw safeChainError(error, options);
|
|
36868
|
+
}
|
|
36869
|
+
};
|
|
36870
|
+
|
|
36811
36871
|
// mcp/lib/testEth.mjs
|
|
36812
36872
|
var amount = (value) => typeof value === "string" && /^\d+(?:\.\d{1,18})?$/.test(value) ? value : null;
|
|
36813
36873
|
var pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -36836,9 +36896,9 @@ var createTestEthRequester = ({
|
|
|
36836
36896
|
await ctx.client.ensureToken();
|
|
36837
36897
|
if (ctx.client.tokenClaims?.scopes?.faucet !== true) throw new Error("This session does not offer test ETH to you.");
|
|
36838
36898
|
const provider = suppliedProvider || createProvider(chainId);
|
|
36839
|
-
const reported = Number(await provider.send("eth_chainId", []));
|
|
36899
|
+
const reported = Number(await chainRequest(() => provider.send("eth_chainId", [])));
|
|
36840
36900
|
if (reported !== chainId) throw new Error(`The RPC for chain ${chainId} reports chain ${reported}; refusing the faucet request.`);
|
|
36841
|
-
const before = await provider.getBalance(wallet.address);
|
|
36901
|
+
const before = await chainRequest(() => provider.getBalance(wallet.address));
|
|
36842
36902
|
checkpoint();
|
|
36843
36903
|
let paid;
|
|
36844
36904
|
try {
|
|
@@ -36872,7 +36932,7 @@ var createTestEthRequester = ({
|
|
|
36872
36932
|
let observed;
|
|
36873
36933
|
try {
|
|
36874
36934
|
observed = await Promise.race([
|
|
36875
|
-
provider.getBalance(wallet.address).catch(() => null),
|
|
36935
|
+
chainRequest(() => provider.getBalance(wallet.address)).catch(() => null),
|
|
36876
36936
|
new Promise((resolve) => {
|
|
36877
36937
|
timer = setTimeout(() => resolve(null), Math.max(0, deadline - now()));
|
|
36878
36938
|
})
|
|
@@ -36895,6 +36955,9 @@ var createTestEthRequester = ({
|
|
|
36895
36955
|
};
|
|
36896
36956
|
};
|
|
36897
36957
|
|
|
36958
|
+
// mcp/lib/participant.mjs
|
|
36959
|
+
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
36960
|
+
|
|
36898
36961
|
// mcp/lib/registrySession.mjs
|
|
36899
36962
|
var import_ethers5 = __toESM(require_lib32(), 1);
|
|
36900
36963
|
|
|
@@ -38139,6 +38202,40 @@ function summarizeQuadraticAllocations(responses, question) {
|
|
|
38139
38202
|
return { options, totalResponders, excludedResponses };
|
|
38140
38203
|
}
|
|
38141
38204
|
|
|
38205
|
+
// shared/questions/questionId.mjs
|
|
38206
|
+
var validOptions = (options) => Array.isArray(options) ? options.filter((o) => o && o.trim() !== "") : [];
|
|
38207
|
+
var legacyPreimage = (type, prompt, options, singleSelect, voiceCredits) => {
|
|
38208
|
+
let preimage = `${type}:${(prompt || "").trim().toLowerCase()}`;
|
|
38209
|
+
const validOpts = validOptions(options);
|
|
38210
|
+
if (type === "multichoice") {
|
|
38211
|
+
if (validOpts.length > 0)
|
|
38212
|
+
preimage += `:${validOpts.map((o) => o.trim()).join(",").toLowerCase()}`;
|
|
38213
|
+
if (singleSelect) preimage += ":single";
|
|
38214
|
+
}
|
|
38215
|
+
if (type === "quadratic") {
|
|
38216
|
+
preimage += `:${JSON.stringify(validOpts.map((option) => option.trim().toLowerCase()))}:${voiceCredits}`;
|
|
38217
|
+
}
|
|
38218
|
+
return preimage;
|
|
38219
|
+
};
|
|
38220
|
+
var legacyIsAmbiguous = (type, options, singleSelect) => {
|
|
38221
|
+
if (type !== "multichoice") return false;
|
|
38222
|
+
const labels = validOptions(options).map((o) => o.trim().toLowerCase());
|
|
38223
|
+
return labels.length === 0 || labels.some((label) => label.includes(":") || label.includes(",")) || !singleSelect && labels.length === 1 && labels[0] === "single";
|
|
38224
|
+
};
|
|
38225
|
+
var v2Text = (text4) => (text4 || "").trim().toLowerCase().normalize("NFC");
|
|
38226
|
+
var v2Preimage = (type, prompt, options, singleSelect) => JSON.stringify(["ce-question-id", 2, type, v2Text(prompt), validOptions(options).map(v2Text), !!singleSelect]);
|
|
38227
|
+
var createQuestionIdDerivation = (ethers7) => {
|
|
38228
|
+
const { id: hash } = ethers7.utils;
|
|
38229
|
+
const generateQuestionId2 = (type, prompt, options = [], singleSelect = false, voiceCredits = 99) => hash(
|
|
38230
|
+
legacyIsAmbiguous(type, options, singleSelect) ? v2Preimage(type, prompt, options, singleSelect) : legacyPreimage(type, prompt, options, singleSelect, voiceCredits)
|
|
38231
|
+
);
|
|
38232
|
+
const questionIdMatchesContent2 = (id, { type, prompt, options = [], singleSelect = false, voiceCredits = 99 } = {}) => {
|
|
38233
|
+
const wanted = String(id).toLowerCase();
|
|
38234
|
+
return hash(legacyPreimage(type, prompt, options, singleSelect, voiceCredits)) === wanted || type === "multichoice" && hash(v2Preimage(type, prompt, options, singleSelect)) === wanted;
|
|
38235
|
+
};
|
|
38236
|
+
return Object.freeze({ generateQuestionId: generateQuestionId2, questionIdMatchesContent: questionIdMatchesContent2 });
|
|
38237
|
+
};
|
|
38238
|
+
|
|
38142
38239
|
// shared/questions/ratingScale.mjs
|
|
38143
38240
|
var trim2 = (value) => String(value == null ? "" : value).trim();
|
|
38144
38241
|
var isObj = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -38207,18 +38304,7 @@ var MAX_COMMENT_LENGTH = 4e3;
|
|
|
38207
38304
|
var trim3 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
|
|
38208
38305
|
var isRecord3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
38209
38306
|
var fail = (error) => ({ ok: false, error });
|
|
38210
|
-
var generateQuestionId
|
|
38211
|
-
let dataToHash = `${type}:${(prompt || "").trim().toLowerCase()}`;
|
|
38212
|
-
const validOptions = Array.isArray(options) ? options.filter((option) => typeof option === "string" && option.trim() !== "") : [];
|
|
38213
|
-
if (type === "multichoice") {
|
|
38214
|
-
if (validOptions.length > 0) dataToHash += `:${validOptions.map((option) => option.trim()).join(",").toLowerCase()}`;
|
|
38215
|
-
if (singleSelect) dataToHash += ":single";
|
|
38216
|
-
}
|
|
38217
|
-
if (type === "quadratic") {
|
|
38218
|
-
dataToHash += `:${JSON.stringify(validOptions.map((option) => option.trim().toLowerCase()))}:${voiceCredits}`;
|
|
38219
|
-
}
|
|
38220
|
-
return import_ethers3.ethers.utils.id(dataToHash);
|
|
38221
|
-
};
|
|
38307
|
+
var { generateQuestionId, questionIdMatchesContent } = createQuestionIdDerivation(import_ethers3.ethers);
|
|
38222
38308
|
var ratingScaleFor = (question = {}) => normalizeRatingScale(question) || { min: 0, max: 10, minLabel: "0", maxLabel: "10" };
|
|
38223
38309
|
var optionsOf = (question) => Array.isArray(question.options) ? question.options.filter((option) => typeof option === "string") : [];
|
|
38224
38310
|
var isSingleSelect = (question) => question.singleSelect === true || question.oneSelectionOnly === true || question.singleChoice === true;
|
|
@@ -38462,6 +38548,51 @@ var parseSessionLink = (link, { allowLocalWorkers = false } = {}) => {
|
|
|
38462
38548
|
};
|
|
38463
38549
|
var buildSessionWebUrl = ({ appOrigin, slug, workerOrigin, kind }) => `${appOrigin}/session/${encodeURIComponent(slug)}${kind === "registry" ? "" : `?worker=${encodeURIComponent(workerOrigin)}`}`;
|
|
38464
38550
|
|
|
38551
|
+
// shared/auth/siweMessage.mjs
|
|
38552
|
+
var normalizeSessionIdHex = (value) => {
|
|
38553
|
+
if (typeof value !== "string" || value !== value.trim()) return "";
|
|
38554
|
+
const lower = value.toLowerCase();
|
|
38555
|
+
const hex = lower.startsWith("0x") ? lower.slice(2) : lower;
|
|
38556
|
+
if (/^[0-9a-f]{32}$/.test(hex) && !/^0+$/.test(hex)) return `0x${hex}`;
|
|
38557
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(lower)) {
|
|
38558
|
+
const uuidHex = lower.replace(/-/g, "");
|
|
38559
|
+
return /^0+$/.test(uuidHex) ? "" : `0x${uuidHex}`;
|
|
38560
|
+
}
|
|
38561
|
+
return "";
|
|
38562
|
+
};
|
|
38563
|
+
var buildSiweMessage = ({
|
|
38564
|
+
address,
|
|
38565
|
+
nonce,
|
|
38566
|
+
chainId,
|
|
38567
|
+
domain,
|
|
38568
|
+
uri,
|
|
38569
|
+
statement,
|
|
38570
|
+
issuedAt,
|
|
38571
|
+
expirationTime,
|
|
38572
|
+
workerUrl,
|
|
38573
|
+
sessionId,
|
|
38574
|
+
sessionSlug
|
|
38575
|
+
}) => {
|
|
38576
|
+
const id = String(sessionId || "").trim().toLowerCase();
|
|
38577
|
+
const slug = sessionSlug === "general" ? "" : String(sessionSlug || "");
|
|
38578
|
+
const sessionResource = id ? `urn:context-engine:session:id:${encodeURIComponent(id)}` : `urn:context-engine:session:slug:${encodeURIComponent(slug)}`;
|
|
38579
|
+
const resources = workerUrl ? `
|
|
38580
|
+
Resources:
|
|
38581
|
+
- ${new URL(String(workerUrl)).origin}
|
|
38582
|
+
- ${sessionResource}` : "";
|
|
38583
|
+
return `${domain} wants you to sign in with your Ethereum account:
|
|
38584
|
+
${address}
|
|
38585
|
+
|
|
38586
|
+
${statement}
|
|
38587
|
+
|
|
38588
|
+
URI: ${uri}
|
|
38589
|
+
Version: 1
|
|
38590
|
+
Chain ID: ${chainId}
|
|
38591
|
+
Nonce: ${nonce}
|
|
38592
|
+
Issued At: ${issuedAt}
|
|
38593
|
+
Expiration Time: ${expirationTime}${resources}`;
|
|
38594
|
+
};
|
|
38595
|
+
|
|
38465
38596
|
// mcp/lib/workerClient.mjs
|
|
38466
38597
|
var REQUEST_TIMEOUT_MS = 2e4;
|
|
38467
38598
|
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
@@ -38521,17 +38652,6 @@ var readLimitedText = async (response, maxBytes = MAX_RESPONSE_BYTES) => {
|
|
|
38521
38652
|
}
|
|
38522
38653
|
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString("utf8");
|
|
38523
38654
|
};
|
|
38524
|
-
var normalizeSessionIdHex = (value) => {
|
|
38525
|
-
if (typeof value !== "string" || value !== value.trim()) return "";
|
|
38526
|
-
const lower = value.toLowerCase();
|
|
38527
|
-
const hex = lower.startsWith("0x") ? lower.slice(2) : lower;
|
|
38528
|
-
if (/^[0-9a-f]{32}$/.test(hex) && !/^0+$/.test(hex)) return `0x${hex}`;
|
|
38529
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(lower)) {
|
|
38530
|
-
const uuidHex = lower.replace(/-/g, "");
|
|
38531
|
-
return /^0+$/.test(uuidHex) ? "" : `0x${uuidHex}`;
|
|
38532
|
-
}
|
|
38533
|
-
return "";
|
|
38534
|
-
};
|
|
38535
38655
|
var resolveSessionIdHex = (config) => {
|
|
38536
38656
|
const present = (value) => value !== void 0 && value !== null && value !== "";
|
|
38537
38657
|
const sessionId = normalizeSessionIdHex(config?.sessionId);
|
|
@@ -38541,24 +38661,19 @@ var resolveSessionIdHex = (config) => {
|
|
|
38541
38661
|
}
|
|
38542
38662
|
return sessionId || sessionIdHex;
|
|
38543
38663
|
};
|
|
38544
|
-
var
|
|
38545
|
-
|
|
38546
|
-
|
|
38547
|
-
|
|
38548
|
-
|
|
38549
|
-
|
|
38550
|
-
|
|
38551
|
-
|
|
38552
|
-
|
|
38553
|
-
|
|
38554
|
-
|
|
38555
|
-
|
|
38556
|
-
|
|
38557
|
-
Expiration Time: ${expirationTime}
|
|
38558
|
-
Resources:
|
|
38559
|
-
- ${workerOrigin}
|
|
38560
|
-
- ${resource}`;
|
|
38561
|
-
};
|
|
38664
|
+
var buildSiweMessage2 = ({ address, nonce, appOrigin, workerOrigin, sessionId, sessionSlug = "", issuedAt, expirationTime, chainId = 1 }) => buildSiweMessage({
|
|
38665
|
+
address,
|
|
38666
|
+
nonce,
|
|
38667
|
+
domain: new URL(appOrigin).host,
|
|
38668
|
+
uri: appOrigin,
|
|
38669
|
+
workerUrl: workerOrigin,
|
|
38670
|
+
sessionId,
|
|
38671
|
+
sessionSlug,
|
|
38672
|
+
issuedAt,
|
|
38673
|
+
expirationTime,
|
|
38674
|
+
chainId,
|
|
38675
|
+
statement: "Sign in to Context Engine."
|
|
38676
|
+
});
|
|
38562
38677
|
var decodeTokenClaims = (token) => {
|
|
38563
38678
|
try {
|
|
38564
38679
|
const [payload] = text(token).split(".");
|
|
@@ -38679,7 +38794,7 @@ var createWorkerClient = ({ workerOrigin, slug, trustedOrigins, wallet, fetchImp
|
|
|
38679
38794
|
const nonce = text(nonceData?.nonce);
|
|
38680
38795
|
if (!/^[A-Za-z0-9]{8,200}$/.test(nonce)) throw new WorkerRequestError("The Worker did not issue a usable sign-in nonce.");
|
|
38681
38796
|
const issued = new Date(now());
|
|
38682
|
-
const message =
|
|
38797
|
+
const message = buildSiweMessage2({
|
|
38683
38798
|
address,
|
|
38684
38799
|
nonce,
|
|
38685
38800
|
appOrigin: loginOrigin,
|
|
@@ -38744,11 +38859,11 @@ var createWorkerClient = ({ workerOrigin, slug, trustedOrigins, wallet, fetchImp
|
|
|
38744
38859
|
}
|
|
38745
38860
|
return data;
|
|
38746
38861
|
};
|
|
38747
|
-
const upload = async ({ resource, data, tags = [] }) => {
|
|
38862
|
+
const upload = async ({ resource, data, tags = [], requestId = "" }) => {
|
|
38748
38863
|
const result = await request("/storage/upload", {
|
|
38749
38864
|
method: "POST",
|
|
38750
38865
|
auth: true,
|
|
38751
|
-
body: { backend: "cloudflare", resource, contentType: "application/json", payloadEncrypted: false, tags, data }
|
|
38866
|
+
body: { backend: "cloudflare", resource, contentType: "application/json", payloadEncrypted: false, tags, data, ...requestId ? { requestId } : {} }
|
|
38752
38867
|
});
|
|
38753
38868
|
return isRecord4(result?.storageRef) ? result.storageRef : { id: text(result?.id) };
|
|
38754
38869
|
};
|
|
@@ -38793,7 +38908,7 @@ var createWorkerClient = ({ workerOrigin, slug, trustedOrigins, wallet, fetchImp
|
|
|
38793
38908
|
// mcp/lib/registryChain.mjs
|
|
38794
38909
|
var compareChainOrder = (a, b) => Number(a.blockNumber || 0) - Number(b.blockNumber || 0) || Number(a.transactionIndex || 0) - Number(b.transactionIndex || 0) || Number(a.logIndex || 0) - Number(b.logIndex || 0);
|
|
38795
38910
|
var checkRegistryChain = async (provider, chainId) => {
|
|
38796
|
-
const actual = Number(await provider.send("eth_chainId", []));
|
|
38911
|
+
const actual = Number(await chainRequest(() => provider.send("eth_chainId", [])));
|
|
38797
38912
|
if (actual !== chainId) throw new Error(`The RPC is on a different chain (${actual}); expected ${chainId}.`);
|
|
38798
38913
|
};
|
|
38799
38914
|
var findStartBlock = async (provider, createdAt, head) => {
|
|
@@ -38801,7 +38916,7 @@ var findStartBlock = async (provider, createdAt, head) => {
|
|
|
38801
38916
|
let high = head;
|
|
38802
38917
|
while (low < high) {
|
|
38803
38918
|
const mid = Math.floor((low + high) / 2);
|
|
38804
|
-
const block = await provider.getBlock(mid);
|
|
38919
|
+
const block = await chainRequest(() => provider.getBlock(mid));
|
|
38805
38920
|
if (!block || !Number.isFinite(Number(block.timestamp))) throw new Error("Could not locate the session creation block.");
|
|
38806
38921
|
if (Number(block.timestamp) < createdAt) low = mid + 1;
|
|
38807
38922
|
else high = mid;
|
|
@@ -38839,7 +38954,7 @@ var scanRegistryLogs = async ({
|
|
|
38839
38954
|
range = Math.max(1, Math.floor((end - begin + 1) / 2));
|
|
38840
38955
|
continue;
|
|
38841
38956
|
}
|
|
38842
|
-
throw error;
|
|
38957
|
+
throw safeChainError(error);
|
|
38843
38958
|
}
|
|
38844
38959
|
batch = batch.sort((a, b) => descending ? compareChainOrder(b, a) : compareChainOrder(a, b));
|
|
38845
38960
|
let skipped = 0;
|
|
@@ -38971,13 +39086,14 @@ var stopped = (signal) => {
|
|
|
38971
39086
|
if (signal?.aborted) throw new Error("Stopped before the chain transaction was sent.");
|
|
38972
39087
|
};
|
|
38973
39088
|
var createRegistryWriter = ({ provider, chainId, wallet, surveys, slug, remember }) => {
|
|
39089
|
+
const checkChain = () => checkRegistryChain(provider, chainId);
|
|
38974
39090
|
const prepare = async ({ client, gasLimit, fund, signal }) => {
|
|
38975
39091
|
stopped(signal);
|
|
38976
|
-
await
|
|
38977
|
-
const gasPrice = await provider.getGasPrice();
|
|
39092
|
+
await checkChain();
|
|
39093
|
+
const gasPrice = await chainRequest(() => provider.getGasPrice());
|
|
38978
39094
|
const cost = gasPrice.mul(gasLimit);
|
|
38979
39095
|
if (cost.gt(MAX_COST)) throw new Error("This transaction would exceed the 0.005 ETH gas cap; retry when gas is cheaper.");
|
|
38980
|
-
let balance = await provider.getBalance(wallet.address);
|
|
39096
|
+
let balance = await chainRequest(() => provider.getBalance(wallet.address));
|
|
38981
39097
|
const shortfall = `Fund ${wallet.address} with test ETH on chain ${chainId}; this transaction needs up to ${import_ethers4.ethers.utils.formatEther(cost)} ETH.`;
|
|
38982
39098
|
let fundingNote = "";
|
|
38983
39099
|
if (balance.lt(cost) && fund) {
|
|
@@ -38991,7 +39107,7 @@ var createRegistryWriter = ({ provider, chainId, wallet, surveys, slug, remember
|
|
|
38991
39107
|
}
|
|
38992
39108
|
if (funding?.sent) fundingNote = ` Faucet transaction ${funding.txHash} was sent; check it before requesting another payout.`;
|
|
38993
39109
|
else if (funding?.reason === "balance_high") fundingNote = " The faucet refused because the balance is above its threshold.";
|
|
38994
|
-
balance = await provider.getBalance(wallet.address).catch(() => null);
|
|
39110
|
+
balance = await chainRequest(() => provider.getBalance(wallet.address)).catch(() => null);
|
|
38995
39111
|
if (!balance) throw new Error(`${shortfall}${fundingNote} The current balance could not be checked.`);
|
|
38996
39112
|
}
|
|
38997
39113
|
}
|
|
@@ -39021,7 +39137,7 @@ var createRegistryWriter = ({ provider, chainId, wallet, surveys, slug, remember
|
|
|
39021
39137
|
})
|
|
39022
39138
|
]);
|
|
39023
39139
|
} catch {
|
|
39024
|
-
throw new Error(`Transaction ${tx.hash} was submitted but could not be confirmed. Check it before retrying.`);
|
|
39140
|
+
throw Object.assign(new Error(`Transaction ${tx.hash} was submitted but could not be confirmed. Check it before retrying.`), { txHash: tx.hash, reconciliationRequired: true });
|
|
39025
39141
|
} finally {
|
|
39026
39142
|
clearTimeout(timer);
|
|
39027
39143
|
}
|
|
@@ -39046,22 +39162,22 @@ var createRegistryWriter = ({ provider, chainId, wallet, surveys, slug, remember
|
|
|
39046
39162
|
const pointers = [];
|
|
39047
39163
|
for (const record of records) pointers.push(await upload(client, "responses", record.questionID, record, signal));
|
|
39048
39164
|
stopped(signal);
|
|
39049
|
-
await
|
|
39050
|
-
const tx = await surveys.connect(wallet.connect(provider)).submitResponses(ids, pointers, import_ethers4.ethers.constants.HashZero, import_ethers4.ethers.constants.HashZero, fees);
|
|
39165
|
+
await checkChain();
|
|
39166
|
+
const tx = await chainRequest(() => surveys.connect(wallet.connect(provider)).submitResponses(ids, pointers, import_ethers4.ethers.constants.HashZero, import_ethers4.ethers.constants.HashZero, fees), { submission: true });
|
|
39051
39167
|
const receipt = await confirm(tx, "ResponsesSubmitted", ids);
|
|
39052
39168
|
records.forEach((record, index) => remember("responses", record.questionID, record, pointers[index], receipt));
|
|
39053
39169
|
return { txHash: tx.hash, receipt };
|
|
39054
39170
|
};
|
|
39055
39171
|
const addStatement = async ({ client, record, fund, signal }) => {
|
|
39056
|
-
await
|
|
39057
|
-
if (await surveys.questionHashes(record.id) !== import_ethers4.ethers.constants.HashZero) {
|
|
39172
|
+
await checkChain();
|
|
39173
|
+
if (await chainRequest(() => surveys.questionHashes(record.id)) !== import_ethers4.ethers.constants.HashZero) {
|
|
39058
39174
|
throw new Error("This statement already exists on chain, possibly in another session; reword it to add a new statement.");
|
|
39059
39175
|
}
|
|
39060
39176
|
const fees = await prepare({ client, gasLimit: 4e5, fund, signal });
|
|
39061
39177
|
const pointer = await upload(client, "questions", record.id, record, signal);
|
|
39062
39178
|
stopped(signal);
|
|
39063
|
-
await
|
|
39064
|
-
const tx = await surveys.connect(wallet.connect(provider)).addQuestions([record.id], [pointer], [import_ethers4.ethers.constants.HashZero], fees);
|
|
39179
|
+
await checkChain();
|
|
39180
|
+
const tx = await chainRequest(() => surveys.connect(wallet.connect(provider)).addQuestions([record.id], [pointer], [import_ethers4.ethers.constants.HashZero], fees), { submission: true });
|
|
39065
39181
|
const receipt = await confirm(tx, "QuestionsAdded", [record.id]);
|
|
39066
39182
|
remember("questions", record.id, record, pointer, receipt);
|
|
39067
39183
|
return { txHash: tx.hash };
|
|
@@ -39134,13 +39250,13 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39134
39250
|
const loadConfig = async () => {
|
|
39135
39251
|
await checkRegistryChain(provider, chainId);
|
|
39136
39252
|
const slug = info.slug === "general" ? "" : info.slug;
|
|
39137
|
-
const row = await registry.getSessionBySlug(slug);
|
|
39253
|
+
const row = await chainRequest(() => registry.getSessionBySlug(slug));
|
|
39138
39254
|
if (normalized(row[0]) !== slug) throw new Error("The registry returned a different session.");
|
|
39139
39255
|
if (text(row[3])) throw new Error("Encrypted registry metadata is unsupported; use an unencrypted session.");
|
|
39140
39256
|
if (Number(row[1]) !== chainId) throw new Error("The session uses a different chain from this registry.");
|
|
39141
39257
|
const metadata = await readRegistryMetadata(row[2], { fetchImpl });
|
|
39142
39258
|
checkMetadata(metadata);
|
|
39143
|
-
const fields = await registry.getSessionFields(slug, ["corsWorkerUrl"]);
|
|
39259
|
+
const fields = await chainRequest(() => registry.getSessionFields(slug, ["corsWorkerUrl"]));
|
|
39144
39260
|
const workerUrl = text(fields[0]) || text(metadata.corsWorkerUrl);
|
|
39145
39261
|
if (/^\s*(?:\{|enc:|lit:)/i.test(workerUrl)) throw new Error("Encrypted Worker URLs are unsupported; use an unencrypted registry session.");
|
|
39146
39262
|
const nextOrigin = normalizeWorkerOrigin(workerUrl, { allowLocalWorkers: config.allowLocalWorkers });
|
|
@@ -39163,7 +39279,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39163
39279
|
};
|
|
39164
39280
|
if (startBlock === void 0) {
|
|
39165
39281
|
const configured = Number(metadata.blockLimits?.start);
|
|
39166
|
-
const head = await provider.getBlockNumber();
|
|
39282
|
+
const head = await chainRequest(() => provider.getBlockNumber());
|
|
39167
39283
|
startBlock = metadata.blockLimits?.start !== void 0 && Number.isSafeInteger(configured) && configured >= 0 ? configured : await findStartBlock(provider, Number(row[5]), head);
|
|
39168
39284
|
questionCursor = { block: startBlock, offset: 0 };
|
|
39169
39285
|
}
|
|
@@ -39181,28 +39297,66 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39181
39297
|
return payload;
|
|
39182
39298
|
};
|
|
39183
39299
|
const filter = (eventName) => ({ address: surveys.address, topics: [surveys.interface.getEventTopic(eventName)] });
|
|
39300
|
+
const readBlock = (number, blocks) => {
|
|
39301
|
+
if (!blocks.has(number)) blocks.set(number, chainRequest(() => provider.getBlock(number)).catch(() => null));
|
|
39302
|
+
return blocks.get(number);
|
|
39303
|
+
};
|
|
39304
|
+
const readHeadMark = async (number, blocks) => {
|
|
39305
|
+
const block = await readBlock(number, blocks);
|
|
39306
|
+
return block?.hash ? { number, hash: block.hash } : null;
|
|
39307
|
+
};
|
|
39308
|
+
const historyReplaced = async (mark, blocks) => {
|
|
39309
|
+
if (!mark?.hash) return false;
|
|
39310
|
+
const block = await readBlock(mark.number, blocks);
|
|
39311
|
+
return block?.hash ? block.hash !== mark.hash : null;
|
|
39312
|
+
};
|
|
39313
|
+
const replacedWrite = (entry, blocks = /* @__PURE__ */ new Map()) => historyReplaced(entry?.blockHash ? { number: entry.blockNumber, hash: entry.blockHash } : null, blocks);
|
|
39314
|
+
let questionMark = null;
|
|
39315
|
+
const visibleQuestions = () => {
|
|
39316
|
+
const blocked = new Set((Array.isArray(sessionConfig.BLOCKED_QUESTION_IDS) ? sessionConfig.BLOCKED_QUESTION_IDS : []).map(normalized));
|
|
39317
|
+
return [...questions.values()].filter((entry) => !blocked.has(entry.id));
|
|
39318
|
+
};
|
|
39184
39319
|
const readQuestions = async ({ signal } = {}) => {
|
|
39185
39320
|
const deadline = now() + 9e4;
|
|
39321
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
39186
39322
|
let complete = true;
|
|
39323
|
+
const replaced = await historyReplaced(questionMark, blocks);
|
|
39324
|
+
if (replaced === null) return { items: visibleQuestions(), complete: false };
|
|
39325
|
+
if (replaced) {
|
|
39326
|
+
questions.clear();
|
|
39327
|
+
pending.clear();
|
|
39328
|
+
questionCursor = { block: startBlock, offset: 0 };
|
|
39329
|
+
questionMark = null;
|
|
39330
|
+
}
|
|
39331
|
+
for (const [id, entry] of questions) {
|
|
39332
|
+
if (!entry.local) continue;
|
|
39333
|
+
const replaced2 = await replacedWrite(entry, blocks);
|
|
39334
|
+
if (replaced2 === null) complete = false;
|
|
39335
|
+
if (replaced2) questions.delete(id);
|
|
39336
|
+
}
|
|
39187
39337
|
if (pending.size < MAX_CACHE && questions.size < MAX_QUESTIONS) {
|
|
39188
|
-
const
|
|
39189
|
-
|
|
39190
|
-
|
|
39191
|
-
|
|
39192
|
-
|
|
39193
|
-
|
|
39194
|
-
|
|
39195
|
-
|
|
39196
|
-
|
|
39197
|
-
|
|
39198
|
-
|
|
39199
|
-
complete = scanned.complete;
|
|
39200
|
-
for (const log2 of scanned.logs) {
|
|
39201
|
-
const event = surveys.interface.parseLog(log2);
|
|
39202
|
-
event.args.questionIds.forEach((id, index) => {
|
|
39203
|
-
if (event.args.surveyIds[index] === import_ethers5.ethers.constants.HashZero && !questions.has(normalized(id))) pending.set(normalized(id), log2);
|
|
39338
|
+
const head = await readHeadMark(await chainRequest(() => provider.getBlockNumber()), blocks);
|
|
39339
|
+
if (head) {
|
|
39340
|
+
const scanned = await scanRegistryLogs({
|
|
39341
|
+
provider,
|
|
39342
|
+
filter: filter("QuestionsAdded"),
|
|
39343
|
+
cursor: questionCursor,
|
|
39344
|
+
toBlock: head.number,
|
|
39345
|
+
maxLogs: 1e3,
|
|
39346
|
+
deadline,
|
|
39347
|
+
now,
|
|
39348
|
+
signal
|
|
39204
39349
|
});
|
|
39205
|
-
|
|
39350
|
+
questionCursor = scanned.cursor;
|
|
39351
|
+
complete = complete && scanned.complete;
|
|
39352
|
+
if (replaced !== null && (!questionMark || head.number >= questionMark.number)) questionMark = head;
|
|
39353
|
+
for (const log2 of scanned.logs) {
|
|
39354
|
+
const event = surveys.interface.parseLog(log2);
|
|
39355
|
+
event.args.questionIds.forEach((id, index) => {
|
|
39356
|
+
if (event.args.surveyIds[index] === import_ethers5.ethers.constants.HashZero && !questions.has(normalized(id))) pending.set(normalized(id), log2);
|
|
39357
|
+
});
|
|
39358
|
+
}
|
|
39359
|
+
} else complete = false;
|
|
39206
39360
|
} else complete = false;
|
|
39207
39361
|
const work = [...pending.entries()];
|
|
39208
39362
|
let next = 0;
|
|
@@ -39210,7 +39364,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39210
39364
|
while (next < work.length && now() < deadline && !signal?.aborted && questions.size < MAX_QUESTIONS) {
|
|
39211
39365
|
const [id, log2] = work[next++];
|
|
39212
39366
|
try {
|
|
39213
|
-
const pointer = await surveys.getQuestionHash(id);
|
|
39367
|
+
const pointer = await chainRequest(() => surveys.getQuestionHash(id));
|
|
39214
39368
|
const payload = await readPayload(pointer);
|
|
39215
39369
|
const matches = registryQuestionMatchesSession(payload, sessionConfig);
|
|
39216
39370
|
if (matches) questions.set(id, { id, payload: { ...payload, id }, createdAt: String(log2.blockNumber).padStart(16, "0"), ...log2 });
|
|
@@ -39220,8 +39374,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39220
39374
|
}
|
|
39221
39375
|
}
|
|
39222
39376
|
}));
|
|
39223
|
-
|
|
39224
|
-
return { items: [...questions.values()].filter((entry) => !blocked.has(entry.id)), complete: complete && !pending.size && questions.size < MAX_QUESTIONS };
|
|
39377
|
+
return { items: visibleQuestions(), complete: complete && !pending.size && questions.size < MAX_QUESTIONS };
|
|
39225
39378
|
};
|
|
39226
39379
|
const loadQuestions = (args) => {
|
|
39227
39380
|
if (!loadingQuestions) loadingQuestions = readQuestions(args).finally(() => {
|
|
@@ -39232,17 +39385,41 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39232
39385
|
const readAnswers = async ({ questionIds, maxRecords = 1e4, mine = false, signal } = {}) => {
|
|
39233
39386
|
const known = new Set(questionIds);
|
|
39234
39387
|
const deadline = now() + 9e4;
|
|
39388
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
39389
|
+
let verified = true;
|
|
39235
39390
|
const responseFilter = filter("ResponsesSubmitted");
|
|
39236
39391
|
if (mine) responseFilter.topics.push(import_ethers5.ethers.utils.hexZeroPad(wallet.address, 32));
|
|
39237
|
-
const head = await provider.getBlockNumber();
|
|
39392
|
+
const head = await chainRequest(() => provider.getBlockNumber());
|
|
39238
39393
|
const scope = [...known].sort().join(",");
|
|
39239
39394
|
let state = answerScans.get(mine);
|
|
39240
|
-
|
|
39241
|
-
|
|
39395
|
+
const replaced = !!state && state.scope === scope && head >= state.head && await historyReplaced(state.mark, blocks);
|
|
39396
|
+
if (replaced === null) {
|
|
39397
|
+
const held = new Map((state.result?.entries || []).map((entry) => [`${entry.questionId}:${normalized(entry.responder)}`, entry]));
|
|
39398
|
+
const chainOrders2 = new Map(state.result?.chainOrders || []);
|
|
39399
|
+
for (const entry of localAnswers.values()) {
|
|
39400
|
+
if (!known.has(entry.questionId)) continue;
|
|
39401
|
+
const key = `${entry.questionId}:${normalized(entry.responder)}`;
|
|
39402
|
+
if ([state.latest.get(key), held.get(key)].some((previous) => previous && compareChainOrder(entry, previous) < 0)) continue;
|
|
39403
|
+
chainOrders2.set(entry.questionId, entry);
|
|
39404
|
+
held.delete(key);
|
|
39405
|
+
if (registryResponseMatchesSession(entry.payload, sessionConfig) && entry.payload.answer?.encrypted !== true && normalized(entry.payload.questionID || entry.payload.questionId) === entry.questionId) held.set(key, entry);
|
|
39406
|
+
}
|
|
39407
|
+
return {
|
|
39408
|
+
entries: [...held.values()].sort((a, b) => compareChainOrder(b, a)).slice(0, maxRecords),
|
|
39409
|
+
complete: false,
|
|
39410
|
+
chainOrders: chainOrders2
|
|
39411
|
+
};
|
|
39412
|
+
}
|
|
39413
|
+
if (!state || state.scope !== scope || head < state.head || replaced) {
|
|
39414
|
+
state = { scope, head: startBlock - 1, mark: null, ranges: [], latest: /* @__PURE__ */ new Map(), capped: false };
|
|
39242
39415
|
} else state = { ...state, ranges: state.ranges.map((range) => ({ ...range })), latest: new Map(state.latest) };
|
|
39243
39416
|
if (head > state.head) {
|
|
39244
|
-
|
|
39245
|
-
|
|
39417
|
+
const mark = await readHeadMark(head, blocks);
|
|
39418
|
+
if (mark) {
|
|
39419
|
+
state.ranges.unshift({ from: Math.max(startBlock, state.head + 1), cursor: { block: head, offset: 0 } });
|
|
39420
|
+
state.head = head;
|
|
39421
|
+
if (replaced !== null) state.mark = mark;
|
|
39422
|
+
} else verified = false;
|
|
39246
39423
|
}
|
|
39247
39424
|
if (state.ranges.length > 64) {
|
|
39248
39425
|
const oldest = state.ranges.pop();
|
|
@@ -39295,8 +39472,14 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39295
39472
|
answerScans.set(mine, state);
|
|
39296
39473
|
const scannedAll = !state.ranges.length && !state.capped;
|
|
39297
39474
|
const latest = new Map(state.latest);
|
|
39298
|
-
for (const entry of localAnswers
|
|
39475
|
+
for (const [id, entry] of localAnswers) {
|
|
39299
39476
|
if (!known.has(entry.questionId)) continue;
|
|
39477
|
+
const replaced2 = await replacedWrite(entry, blocks);
|
|
39478
|
+
if (replaced2 === null) verified = false;
|
|
39479
|
+
if (replaced2) {
|
|
39480
|
+
localAnswers.delete(id);
|
|
39481
|
+
continue;
|
|
39482
|
+
}
|
|
39300
39483
|
if (!scannedAll && entry.blockNumber <= head) continue;
|
|
39301
39484
|
const key = `${entry.questionId}:${normalized(entry.responder)}`;
|
|
39302
39485
|
if (!latest.has(key) || compareChainOrder(entry, latest.get(key)) > 0) latest.set(key, { ...entry, local: true });
|
|
@@ -39304,8 +39487,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39304
39487
|
const candidates = [...latest.values()].sort((a, b) => compareChainOrder(b, a));
|
|
39305
39488
|
const chainOrders = new Map(candidates.filter((entry) => normalized(entry.responder) === normalized(wallet.address)).map((entry) => [entry.questionId, entry]));
|
|
39306
39489
|
const entries = [];
|
|
39307
|
-
|
|
39308
|
-
let complete = scannedAll && candidates.length <= maxRecords;
|
|
39490
|
+
let complete = verified && scannedAll && candidates.length <= maxRecords;
|
|
39309
39491
|
let next = 0;
|
|
39310
39492
|
const selected = candidates.slice(0, maxRecords);
|
|
39311
39493
|
await Promise.all(Array.from({ length: Math.min(6, selected.length) }, async () => {
|
|
@@ -39316,20 +39498,21 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39316
39498
|
}
|
|
39317
39499
|
const entry = selected[next++];
|
|
39318
39500
|
try {
|
|
39319
|
-
const pointer = entry.local ? entry.storageId : await surveys.getResponse(entry.responder, entry.questionId);
|
|
39501
|
+
const pointer = entry.local ? entry.storageId : await chainRequest(() => surveys.getResponse(entry.responder, entry.questionId));
|
|
39320
39502
|
const payload = await readPayload(pointer);
|
|
39321
39503
|
if (!registryResponseMatchesSession(payload, sessionConfig) || payload.answer?.encrypted === true) continue;
|
|
39322
39504
|
if (normalized(payload.questionID || payload.questionId) !== entry.questionId) continue;
|
|
39323
|
-
|
|
39324
|
-
|
|
39325
|
-
const createdAt = block?.timestamp ? new Date(Number(block.timestamp) * 1e3).toISOString() : "";
|
|
39505
|
+
const block = await readBlock(entry.blockNumber, blocks);
|
|
39506
|
+
if (!block) complete = false;
|
|
39507
|
+
const createdAt = block?.timestamp ? new Date(Number(block.timestamp) * 1e3).toISOString() : entry.createdAt || "";
|
|
39326
39508
|
entries.push({ ...entry, storageId: pointer, createdAt, payload });
|
|
39327
39509
|
} catch {
|
|
39328
39510
|
complete = false;
|
|
39329
39511
|
}
|
|
39330
39512
|
}
|
|
39331
39513
|
}));
|
|
39332
|
-
|
|
39514
|
+
state.result = { entries, complete, chainOrders };
|
|
39515
|
+
return state.result;
|
|
39333
39516
|
};
|
|
39334
39517
|
const writer = createRegistryWriter({ provider, chainId, wallet, surveys, slug: info.slug, remember: (resource, id, payload, pointer, receipt) => {
|
|
39335
39518
|
cache(pointer, payload);
|
|
@@ -39340,18 +39523,33 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
|
|
|
39340
39523
|
storageId: pointer,
|
|
39341
39524
|
responder: wallet.address,
|
|
39342
39525
|
blockNumber: receipt.blockNumber,
|
|
39526
|
+
blockHash: receipt.blockHash,
|
|
39343
39527
|
transactionIndex: receipt.transactionIndex,
|
|
39344
39528
|
logIndex: receipt.logs.at(-1)?.logIndex || 0,
|
|
39345
39529
|
createdAt: new Date(now()).toISOString()
|
|
39346
39530
|
};
|
|
39347
|
-
if (resource === "questions") questions.set(id, entry);
|
|
39531
|
+
if (resource === "questions") questions.set(id, { ...entry, local: true });
|
|
39348
39532
|
else localAnswers.set(id, entry);
|
|
39349
39533
|
} });
|
|
39350
|
-
return { loadConfig, loadQuestions, readAnswers, ...writer, get config() {
|
|
39534
|
+
return { loadConfig, loadQuestions, readAnswers, replacedWrite, ...writer, get config() {
|
|
39351
39535
|
return sessionConfig;
|
|
39352
39536
|
}, workerOrigin };
|
|
39353
39537
|
};
|
|
39354
39538
|
|
|
39539
|
+
// shared/storage/canonicalStorageVersions.mjs
|
|
39540
|
+
var trim5 = (value) => (typeof value === "string" ? value : value == null ? "" : String(value)).trim();
|
|
39541
|
+
var compareStorageIds = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
39542
|
+
var compareVersionAge = (left, right) => (Number(left.createdAtMs) || 0) - (Number(right.createdAtMs) || 0) || compareStorageIds(trim5(left.storageRefId), trim5(right.storageRefId));
|
|
39543
|
+
var isAdminStorageVersion = (candidate) => candidate.uploadAuthority === "admin";
|
|
39544
|
+
var isPreferredStorageVersion = (candidate, current) => {
|
|
39545
|
+
if (!current) return true;
|
|
39546
|
+
const candidateAdmin = isAdminStorageVersion(candidate);
|
|
39547
|
+
const currentAdmin = isAdminStorageVersion(current);
|
|
39548
|
+
if (candidateAdmin !== currentAdmin) return candidateAdmin;
|
|
39549
|
+
const age = compareVersionAge(candidate, current);
|
|
39550
|
+
return candidateAdmin ? age > 0 : age < 0;
|
|
39551
|
+
};
|
|
39552
|
+
|
|
39355
39553
|
// mcp/lib/results.mjs
|
|
39356
39554
|
var text2 = (value) => typeof value === "string" ? value.trim() : "";
|
|
39357
39555
|
var MAX_FREEFORM_SAMPLES = 5;
|
|
@@ -39517,12 +39715,12 @@ var REVEAL_WAIT_MS = 9e4;
|
|
|
39517
39715
|
var POLL_MS = 2e3;
|
|
39518
39716
|
var GAS_HEADROOM_PERCENT = 130;
|
|
39519
39717
|
var PASSWORD_CLAIM_GAS_BUDGET = 4e5;
|
|
39520
|
-
var
|
|
39718
|
+
var trim6 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
|
|
39521
39719
|
var isRecord5 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
39522
39720
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
39523
39721
|
var { deriveGroupPasswordWallet } = createGroupPasswordDerivation(import_ethers6.ethers);
|
|
39524
39722
|
var normalizeGroupPasswordInput = (raw) => {
|
|
39525
|
-
const compact =
|
|
39723
|
+
const compact = trim6(raw).replace(/\s+/g, "");
|
|
39526
39724
|
if (!compact) return "";
|
|
39527
39725
|
if (import_ethers6.ethers.utils.isHexString(compact)) {
|
|
39528
39726
|
try {
|
|
@@ -39534,7 +39732,7 @@ var normalizeGroupPasswordInput = (raw) => {
|
|
|
39534
39732
|
return compact;
|
|
39535
39733
|
};
|
|
39536
39734
|
var decodeInviteCode = (raw) => {
|
|
39537
|
-
let code =
|
|
39735
|
+
let code = trim6(raw);
|
|
39538
39736
|
const lower = code.toLowerCase();
|
|
39539
39737
|
if (lower.startsWith("inv:")) code = code.slice(4).trim();
|
|
39540
39738
|
else if (lower.startsWith("invite:")) code = code.slice(7).trim();
|
|
@@ -39562,13 +39760,13 @@ var accessConditionsOf = (config) => [
|
|
|
39562
39760
|
config?.sessionModeProfile?.encryption?.accessConditions,
|
|
39563
39761
|
config?.storageProfile?.payloadAccessControl?.accessConditions
|
|
39564
39762
|
].flatMap((document) => Array.isArray(document?.conditions) ? document.conditions : []);
|
|
39565
|
-
var conditionAddresses = (condition) => [condition?.contract, condition?.address, ...Array.isArray(condition?.sbtAddresses) ? condition.sbtAddresses : []].map((value) =>
|
|
39763
|
+
var conditionAddresses = (condition) => [condition?.contract, condition?.address, ...Array.isArray(condition?.sbtAddresses) ? condition.sbtAddresses : []].map((value) => trim6(value).toLowerCase()).filter(Boolean);
|
|
39566
39764
|
var positiveChainId = (value) => {
|
|
39567
39765
|
const number = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : NaN;
|
|
39568
39766
|
return Number.isSafeInteger(number) && number > 0 ? number : 0;
|
|
39569
39767
|
};
|
|
39570
39768
|
var resolveSbtChainId = (config, sbtAddress) => {
|
|
39571
|
-
const target =
|
|
39769
|
+
const target = trim6(sbtAddress).toLowerCase();
|
|
39572
39770
|
const condition = accessConditionsOf(config).find(
|
|
39573
39771
|
(entry) => entry?.kind === "sbt_onchain" && conditionAddresses(entry).includes(target) && positiveChainId(entry.chainId)
|
|
39574
39772
|
);
|
|
@@ -39586,19 +39784,13 @@ var listSessionSbts = (config) => {
|
|
|
39586
39784
|
return [...seen.values()];
|
|
39587
39785
|
};
|
|
39588
39786
|
var defaultCreateProvider = (rpcOverride) => (chainId) => {
|
|
39589
|
-
const url =
|
|
39787
|
+
const url = trim6(rpcOverride) || import_rpcDefaults3.default.getPublicRpcUrls(chainId)[0] || import_rpcDefaults3.default.getPathRpcUrl(chainId);
|
|
39590
39788
|
if (!url) throw new Error(`No public RPC is known for chain ${chainId}; set CE_MCP_RPC_URL.`);
|
|
39591
39789
|
return new import_ethers6.ethers.providers.StaticJsonRpcProvider(url, chainId);
|
|
39592
39790
|
};
|
|
39593
39791
|
var ClaimError = class extends Error {
|
|
39594
39792
|
};
|
|
39595
|
-
var
|
|
39596
|
-
var safeMessage = (error, fallback) => {
|
|
39597
|
-
if (error instanceof ClaimError) return error.message;
|
|
39598
|
-
const reason = [error?.reason, error?.errorName, error?.error?.reason].map((value) => typeof value === "string" ? value.replace(/^execution reverted:?\s*/i, "").trim() : "").find((value) => SAFE_REASON_RE.test(value));
|
|
39599
|
-
const code = typeof error?.code === "string" && /^[A-Z_]{3,40}$/.test(error.code) ? error.code : "";
|
|
39600
|
-
return [fallback, reason || code].filter(Boolean).join(": ");
|
|
39601
|
-
};
|
|
39793
|
+
var safeMessage = (error, fallback) => error instanceof ClaimError ? error.message : [fallback, safeErrorReason(error) || safeErrorCode(error)].filter(Boolean).join(": ");
|
|
39602
39794
|
var createSbtClaimer = ({
|
|
39603
39795
|
config = {},
|
|
39604
39796
|
wallet,
|
|
@@ -39711,7 +39903,7 @@ var createSbtClaimer = ({
|
|
|
39711
39903
|
const claimWithGroupPassword = async ({ ctx, provider, chainId, contract, sbtAddress, password }) => {
|
|
39712
39904
|
const normalized2 = normalizeGroupPasswordInput(password);
|
|
39713
39905
|
if (!normalized2) throw new ClaimError("This SBT needs a group password.");
|
|
39714
|
-
const expectedHash =
|
|
39906
|
+
const expectedHash = trim6(await contract.groupPasswordHash()).toLowerCase();
|
|
39715
39907
|
if (!expectedHash || expectedHash === import_ethers6.ethers.constants.HashZero) throw new ClaimError("This SBT has no group password set.");
|
|
39716
39908
|
const groupWallet = await deriveGroupPasswordWallet({ password: normalized2, sbtAddress });
|
|
39717
39909
|
if (import_ethers6.ethers.utils.solidityKeccak256(["address"], [groupWallet.address]) !== expectedHash) {
|
|
@@ -39745,7 +39937,7 @@ var createSbtClaimer = ({
|
|
|
39745
39937
|
signal = callSignal;
|
|
39746
39938
|
let address;
|
|
39747
39939
|
try {
|
|
39748
|
-
address = import_ethers6.ethers.utils.getAddress(
|
|
39940
|
+
address = import_ethers6.ethers.utils.getAddress(trim6(sbtAddress));
|
|
39749
39941
|
} catch {
|
|
39750
39942
|
throw new ClaimError("Pass the SBT contract address (0x\u2026).");
|
|
39751
39943
|
}
|
|
@@ -39778,7 +39970,7 @@ var createSbtClaimer = ({
|
|
|
39778
39970
|
const args = { ctx, provider, chainId, contract, sbtAddress: address };
|
|
39779
39971
|
let transactions;
|
|
39780
39972
|
if (mode === "public") transactions = [(await send({ ...args, method: "claim", args: [] })).transactionHash];
|
|
39781
|
-
else if (mode === "password") transactions = await claimWithCommitReveal({ ...args, password:
|
|
39973
|
+
else if (mode === "password") transactions = await claimWithCommitReveal({ ...args, password: trim6(claimPassword) || trim6(groupPassword) });
|
|
39782
39974
|
else if (mode === "group_password") transactions = await claimWithGroupPassword({ ...args, password: groupPassword });
|
|
39783
39975
|
else transactions = await claimWithInviteCode({ ...args, code: inviteCode || groupPassword });
|
|
39784
39976
|
return { ...base, mode, status: "claimed", transactions };
|
|
@@ -39807,7 +39999,7 @@ var MAX_TITLE_LENGTH = 120;
|
|
|
39807
39999
|
var MAX_CONTEXT_PARAGRAPHS = 4;
|
|
39808
40000
|
var MAX_CONTEXT_LENGTH = 2e3;
|
|
39809
40001
|
var MAX_CONTEXT_LINKS = 4;
|
|
39810
|
-
var
|
|
40002
|
+
var trim7 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
|
|
39811
40003
|
var isRecord6 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
39812
40004
|
var sessionKey = (info) => info.kind === "registry" ? `registry ${info.chainId || 11155420} ${info.slug}` : `${info.workerOrigin} ${info.slug}`;
|
|
39813
40005
|
var mapLimit = async (items, limit, task) => {
|
|
@@ -39868,18 +40060,10 @@ var sanitizeQuestion = (payload, id) => {
|
|
|
39868
40060
|
ratingScaleFor(question);
|
|
39869
40061
|
return question;
|
|
39870
40062
|
};
|
|
39871
|
-
var
|
|
39872
|
-
|
|
39873
|
-
|
|
39874
|
-
|
|
39875
|
-
const ranked = [...versions].sort((left, right) => {
|
|
39876
|
-
const leftMatches = left.contentMatches === true ? 0 : 1;
|
|
39877
|
-
const rightMatches = right.contentMatches === true ? 0 : 1;
|
|
39878
|
-
if (leftMatches !== rightMatches) return leftMatches - rightMatches;
|
|
39879
|
-
return left.createdAt < right.createdAt ? -1 : left.createdAt > right.createdAt ? 1 : 0;
|
|
39880
|
-
});
|
|
39881
|
-
return ranked[0];
|
|
39882
|
-
};
|
|
40063
|
+
var chooseQuestionVersion = (versions) => versions.reduce(
|
|
40064
|
+
(chosen, candidate) => isPreferredStorageVersion(candidate, chosen) ? candidate : chosen,
|
|
40065
|
+
null
|
|
40066
|
+
);
|
|
39883
40067
|
var describeQuestion = (question, myAnswer) => {
|
|
39884
40068
|
const options = stringOptions(question);
|
|
39885
40069
|
const entry = {
|
|
@@ -39941,9 +40125,9 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
39941
40125
|
state = await updateAgentState(config.stateDir, mutate);
|
|
39942
40126
|
};
|
|
39943
40127
|
const profile = () => ({
|
|
39944
|
-
displayName:
|
|
39945
|
-
modelId:
|
|
39946
|
-
platform:
|
|
40128
|
+
displayName: trim7(state?.profile?.displayName ?? config.profile.displayName),
|
|
40129
|
+
modelId: trim7(state?.profile?.modelId ?? config.profile.modelId),
|
|
40130
|
+
platform: trim7(state?.profile?.platform ?? config.profile.platform)
|
|
39947
40131
|
});
|
|
39948
40132
|
const trustedOriginsFor = (preferred) => config.trustedAppOrigins.includes(preferred) ? [preferred, ...config.trustedAppOrigins.filter((origin) => origin !== preferred)] : config.trustedAppOrigins;
|
|
39949
40133
|
const openSession = async (info) => {
|
|
@@ -39994,18 +40178,19 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
39994
40178
|
const entry = fresh.sessions[sessionKey(ctx.info)];
|
|
39995
40179
|
if (!isRecord6(entry) || entry.sessionId !== ctx.client.sessionId) return;
|
|
39996
40180
|
entry.answers = isRecord6(entry.answers) ? entry.answers : {};
|
|
39997
|
-
answered.forEach(({ questionId, record, answeredAt, chainOrder }) => {
|
|
40181
|
+
answered.forEach(({ questionId, record, answeredAt, chainOrder, requestId }) => {
|
|
39998
40182
|
entry.answers[questionId] = {
|
|
39999
40183
|
value: record.answer.value,
|
|
40000
40184
|
comment: record.additional.value,
|
|
40001
40185
|
answeredAt,
|
|
40002
40186
|
...ctx.registry && chainOrder ? chainOrder : {}
|
|
40003
40187
|
};
|
|
40188
|
+
if (requestId && entry.pendingUploads?.[questionId]?.requestId === requestId) delete entry.pendingUploads[questionId];
|
|
40004
40189
|
});
|
|
40005
40190
|
});
|
|
40006
40191
|
const resolveSession = async (session) => {
|
|
40007
40192
|
await loadState();
|
|
40008
|
-
const ref =
|
|
40193
|
+
const ref = trim7(session);
|
|
40009
40194
|
if (/^https?:\/\//i.test(ref)) {
|
|
40010
40195
|
const parsed = parseSessionLink(ref, { allowLocalWorkers: config.allowLocalWorkers });
|
|
40011
40196
|
const key2 = sessionKey(parsed);
|
|
@@ -40116,8 +40301,9 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40116
40301
|
list.push({
|
|
40117
40302
|
payload: question,
|
|
40118
40303
|
createdAt: text(item.metadata?.createdAt),
|
|
40119
|
-
|
|
40120
|
-
|
|
40304
|
+
createdAtMs: Date.parse(text(item.metadata?.createdAt)) || 0,
|
|
40305
|
+
storageRefId: text(item.storageRef.id),
|
|
40306
|
+
uploadAuthority: item.metadata?.uploadAuthority
|
|
40121
40307
|
});
|
|
40122
40308
|
versions.set(id, list);
|
|
40123
40309
|
} catch {
|
|
@@ -40195,24 +40381,28 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40195
40381
|
await ensureReady(ctx);
|
|
40196
40382
|
const scan = await scanMyAnswers(ctx);
|
|
40197
40383
|
const mine = new Map(scan.answers);
|
|
40198
|
-
|
|
40384
|
+
let complete = scan.complete;
|
|
40385
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
40199
40386
|
await loadState();
|
|
40200
40387
|
const local = sessionState(ctx).sessionId === ctx.client.sessionId && isRecord6(sessionState(ctx).answers) ? sessionState(ctx).answers : {};
|
|
40201
|
-
|
|
40202
|
-
if (!isRecord6(answer))
|
|
40203
|
-
if (ctx.registry && !complete)
|
|
40388
|
+
for (const [questionId, answer] of Object.entries(local)) {
|
|
40389
|
+
if (!isRecord6(answer)) continue;
|
|
40390
|
+
if (ctx.registry && !scan.complete) continue;
|
|
40204
40391
|
const known = mine.get(questionId);
|
|
40205
40392
|
const chainOrder = scan.chainOrders?.get(questionId);
|
|
40206
|
-
if (ctx.registry && chainOrder && compareChainOrder(answer, chainOrder) < 0)
|
|
40393
|
+
if (ctx.registry && chainOrder && compareChainOrder(answer, chainOrder) < 0) continue;
|
|
40207
40394
|
const newer = ctx.registry ? !known || compareChainOrder(answer, known) > 0 : !known || text(answer.answeredAt) > known.createdAt;
|
|
40208
|
-
if (newer)
|
|
40395
|
+
if (!newer) continue;
|
|
40396
|
+
const replaced = ctx.registry ? await ctx.registry.replacedWrite(answer, blocks) : false;
|
|
40397
|
+
if (replaced === null) complete = false;
|
|
40398
|
+
if (!replaced) {
|
|
40209
40399
|
mine.set(questionId, {
|
|
40210
40400
|
createdAt: text(answer.answeredAt),
|
|
40211
40401
|
...ctx.registry ? { blockNumber: answer.blockNumber, transactionIndex: answer.transactionIndex, logIndex: answer.logIndex } : {},
|
|
40212
40402
|
payload: { answer: { value: answer.value }, additional: { value: answer.comment } }
|
|
40213
40403
|
});
|
|
40214
40404
|
}
|
|
40215
|
-
}
|
|
40405
|
+
}
|
|
40216
40406
|
[...mine.entries()].forEach(([questionId, entry]) => {
|
|
40217
40407
|
const value = entry?.payload?.answer?.value;
|
|
40218
40408
|
if (value === "" || value === null || value === void 0 || Array.isArray(value) && !value.length) mine.delete(questionId);
|
|
@@ -40254,9 +40444,9 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40254
40444
|
},
|
|
40255
40445
|
async joinSession({ link, worker, session, inviteCode, groupPassword, claimPassword } = {}) {
|
|
40256
40446
|
let parsed;
|
|
40257
|
-
if (
|
|
40447
|
+
if (trim7(link)) {
|
|
40258
40448
|
parsed = parseSessionLink(link, { allowLocalWorkers: config.allowLocalWorkers });
|
|
40259
|
-
} else if (
|
|
40449
|
+
} else if (trim7(worker) && trim7(session)) {
|
|
40260
40450
|
parsed = {
|
|
40261
40451
|
appOrigin: config.appOrigin,
|
|
40262
40452
|
slug: normalizeSessionSlug(session),
|
|
@@ -40277,9 +40467,9 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40277
40467
|
const offered = parsed.sbtClaims.filter((claim) => !claim.auto).map((claim) => claim.sbt);
|
|
40278
40468
|
if (offered.length) outcome.sbtsToClaimWithClaimSbt = offered;
|
|
40279
40469
|
const separateCodes = {
|
|
40280
|
-
inviteCode:
|
|
40281
|
-
groupPassword:
|
|
40282
|
-
claimPassword:
|
|
40470
|
+
inviteCode: trim7(inviteCode),
|
|
40471
|
+
groupPassword: trim7(groupPassword),
|
|
40472
|
+
claimPassword: trim7(claimPassword)
|
|
40283
40473
|
};
|
|
40284
40474
|
const hasSeparateCodes = Object.values(separateCodes).some(Boolean);
|
|
40285
40475
|
if (hasSeparateCodes && claims.length !== 1) {
|
|
@@ -40309,7 +40499,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40309
40499
|
})
|
|
40310
40500
|
);
|
|
40311
40501
|
} catch (error) {
|
|
40312
|
-
outcome.sbtClaims.push({ sbt: claim.sbt, status: "failed", error: error
|
|
40502
|
+
outcome.sbtClaims.push({ sbt: claim.sbt, status: "failed", error: describeError(error) });
|
|
40313
40503
|
}
|
|
40314
40504
|
}
|
|
40315
40505
|
tokenClaims = await ctx.client.login().then((claims2) => {
|
|
@@ -40320,7 +40510,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40320
40510
|
outcome.testEth = await ctx.client.describeTestEth();
|
|
40321
40511
|
outcome.signedIn = !loginError;
|
|
40322
40512
|
if (loginError) {
|
|
40323
|
-
outcome.readOnlyReason = loginError
|
|
40513
|
+
outcome.readOnlyReason = describeError(loginError);
|
|
40324
40514
|
outcome.note = "Joined read-only: public questions and results are available, but uploads, answers, statements, and faucet requests need Worker access.";
|
|
40325
40515
|
}
|
|
40326
40516
|
const scopes = isRecord6(tokenClaims?.scopes) ? tokenClaims.scopes : {};
|
|
@@ -40332,7 +40522,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40332
40522
|
await ctx.client.joinGroup(parsed.joinGroupId);
|
|
40333
40523
|
outcome.joinedGroup = parsed.joinGroupId;
|
|
40334
40524
|
} catch (error) {
|
|
40335
|
-
outcome.groupJoinError = error
|
|
40525
|
+
outcome.groupJoinError = describeError(error);
|
|
40336
40526
|
}
|
|
40337
40527
|
}
|
|
40338
40528
|
outcome.description = text(sessionConfig.sessionInfo).slice(0, MAX_CONTEXT_LENGTH);
|
|
@@ -40393,10 +40583,12 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40393
40583
|
const results = [];
|
|
40394
40584
|
const answered = [];
|
|
40395
40585
|
const pendingAnswers = [];
|
|
40586
|
+
const operationId = randomUUID2();
|
|
40396
40587
|
let txHash;
|
|
40588
|
+
let reconciliationRequired = false;
|
|
40397
40589
|
const seen = /* @__PURE__ */ new Set();
|
|
40398
40590
|
for (const item of answers) {
|
|
40399
|
-
const questionId =
|
|
40591
|
+
const questionId = trim7(item?.questionId).toLowerCase();
|
|
40400
40592
|
if (seen.has(questionId)) {
|
|
40401
40593
|
results.push({ questionId, ok: false, error: "This question appears more than once in the call; send one answer per question." });
|
|
40402
40594
|
continue;
|
|
@@ -40436,12 +40628,23 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40436
40628
|
continue;
|
|
40437
40629
|
}
|
|
40438
40630
|
try {
|
|
40439
|
-
const
|
|
40631
|
+
const data = JSON.stringify(built.record);
|
|
40632
|
+
const digest = createHash("sha256").update(data).digest("hex");
|
|
40633
|
+
let requestId;
|
|
40634
|
+
await saveState((fresh) => {
|
|
40635
|
+
const entry = fresh.sessions[sessionKey(ctx.info)];
|
|
40636
|
+
if (!isRecord6(entry) || entry.sessionId !== ctx.client.sessionId) throw new Error("Session identity changed; join again before answering.");
|
|
40637
|
+
entry.pendingUploads = isRecord6(entry.pendingUploads) ? entry.pendingUploads : {};
|
|
40638
|
+
const previous = entry.pendingUploads[questionId];
|
|
40639
|
+
requestId = previous?.payloadDigest === digest && trim7(previous.requestId) ? previous.requestId : `answer:v1:${operationId}:${questionId}`;
|
|
40640
|
+
entry.pendingUploads[questionId] = { payloadDigest: digest, requestId };
|
|
40641
|
+
});
|
|
40642
|
+
const storageRef = await ctx.client.upload({ resource: "responses", data, requestId });
|
|
40440
40643
|
if (text(storageRef?.id)) cacheRecord(ctx, text(storageRef.id), built.record);
|
|
40441
|
-
answered.push({ questionId, record: built.record, answeredAt: text(storageRef?.createdAt) || new Date(now()).toISOString() });
|
|
40644
|
+
answered.push({ questionId, requestId, record: built.record, answeredAt: text(storageRef?.createdAt) || new Date(now()).toISOString() });
|
|
40442
40645
|
results.push({ questionId, ok: true, answer: built.record.answer.value });
|
|
40443
40646
|
} catch (error) {
|
|
40444
|
-
results.push({ questionId, ok: false, error: error
|
|
40647
|
+
results.push({ questionId, ok: false, error: describeError(error) });
|
|
40445
40648
|
}
|
|
40446
40649
|
}
|
|
40447
40650
|
if (pendingAnswers.length) {
|
|
@@ -40456,19 +40659,32 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40456
40659
|
pendingAnswers.forEach(({ questionId, record }) => {
|
|
40457
40660
|
answered.push({ questionId, record, answeredAt: new Date(now()).toISOString(), chainOrder: {
|
|
40458
40661
|
blockNumber: submitted.receipt.blockNumber,
|
|
40662
|
+
blockHash: submitted.receipt.blockHash,
|
|
40459
40663
|
transactionIndex: submitted.receipt.transactionIndex,
|
|
40460
40664
|
logIndex: submitted.receipt.logs.at(-1)?.logIndex || 0
|
|
40461
40665
|
} });
|
|
40462
40666
|
results.push({ questionId, ok: true, answer: record.answer.value });
|
|
40463
40667
|
});
|
|
40464
40668
|
} catch (error) {
|
|
40465
|
-
|
|
40669
|
+
if (typeof error.txHash === "string" && /^0x[0-9a-f]{64}$/i.test(error.txHash)) txHash = error.txHash;
|
|
40670
|
+
reconciliationRequired = error.reconciliationRequired === true;
|
|
40671
|
+
pendingAnswers.forEach(({ questionId }) => results.push({ questionId, ok: false, error: describeError(error) }));
|
|
40672
|
+
}
|
|
40673
|
+
}
|
|
40674
|
+
let warning;
|
|
40675
|
+
if (answered.length) {
|
|
40676
|
+
try {
|
|
40677
|
+
await rememberAnswers(ctx, answered);
|
|
40678
|
+
} catch {
|
|
40679
|
+
ctx.ownScan = null;
|
|
40680
|
+
warning = "Answers were submitted, but the local answer history could not be saved. Do not resubmit them to fix this warning.";
|
|
40466
40681
|
}
|
|
40467
40682
|
}
|
|
40468
|
-
if (answered.length) await rememberAnswers(ctx, answered);
|
|
40469
40683
|
return {
|
|
40470
40684
|
session: ctx.info.slug,
|
|
40471
40685
|
...txHash ? { txHash } : {},
|
|
40686
|
+
...reconciliationRequired ? { reconciliationRequired: true } : {},
|
|
40687
|
+
...warning ? { warning } : {},
|
|
40472
40688
|
submitted: results.filter((result) => result.ok).length,
|
|
40473
40689
|
failed: results.filter((result) => !result.ok).length,
|
|
40474
40690
|
results
|
|
@@ -40586,7 +40802,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40586
40802
|
const ctx = await resolveSession(session);
|
|
40587
40803
|
if (ctx.registry) throw new Error("Worker groups are not available in on-chain sessions.");
|
|
40588
40804
|
await ensureReady(ctx);
|
|
40589
|
-
const id =
|
|
40805
|
+
const id = trim7(groupId);
|
|
40590
40806
|
if (!/^[A-Za-z0-9._:-]{1,128}$/.test(id)) throw new Error("Pass a valid group id.");
|
|
40591
40807
|
checkpoint();
|
|
40592
40808
|
await ctx.client.joinGroup(id);
|
|
@@ -40601,10 +40817,10 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
|
|
|
40601
40817
|
});
|
|
40602
40818
|
const result = await sbt.claim({
|
|
40603
40819
|
ctx,
|
|
40604
|
-
sbtAddress:
|
|
40605
|
-
inviteCode:
|
|
40606
|
-
groupPassword:
|
|
40607
|
-
claimPassword:
|
|
40820
|
+
sbtAddress: trim7(sbtAddress),
|
|
40821
|
+
inviteCode: trim7(inviteCode),
|
|
40822
|
+
groupPassword: trim7(groupPassword),
|
|
40823
|
+
claimPassword: trim7(claimPassword),
|
|
40608
40824
|
signal
|
|
40609
40825
|
});
|
|
40610
40826
|
await ctx.client.login().catch(() => {
|