@agalmic/contextengine 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7618,8 +7618,8 @@ var require_bytes32 = __commonJS({
7618
7618
  var constants_1 = require_lib8();
7619
7619
  var bytes_1 = require_lib2();
7620
7620
  var utf8_1 = require_utf8();
7621
- function formatBytes32String(text4) {
7622
- var bytes = (0, utf8_1.toUtf8Bytes)(text4);
7621
+ function formatBytes32String(text5) {
7622
+ var bytes = (0, utf8_1.toUtf8Bytes)(text5);
7623
7623
  if (bytes.length > 31) {
7624
7624
  throw new Error("bytes32 string must be less than 32 bytes");
7625
7625
  }
@@ -8137,8 +8137,8 @@ var require_id = __commonJS({
8137
8137
  exports.id = void 0;
8138
8138
  var keccak256_1 = require_lib5();
8139
8139
  var strings_1 = require_lib9();
8140
- function id(text4) {
8141
- return (0, keccak256_1.keccak256)((0, strings_1.toUtf8Bytes)(text4));
8140
+ function id(text5) {
8141
+ return (0, keccak256_1.keccak256)((0, strings_1.toUtf8Bytes)(text5));
8142
8142
  }
8143
8143
  exports.id = id;
8144
8144
  }
@@ -21923,13 +21923,13 @@ var require_aes_js = __commonJS({
21923
21923
  targetArray.set(sourceArray, targetStart);
21924
21924
  }
21925
21925
  var convertUtf8 = /* @__PURE__ */ (function() {
21926
- function toBytes(text4) {
21926
+ function toBytes(text5) {
21927
21927
  var result = [], i = 0;
21928
- text4 = encodeURI(text4);
21929
- while (i < text4.length) {
21930
- var c = text4.charCodeAt(i++);
21928
+ text5 = encodeURI(text5);
21929
+ while (i < text5.length) {
21930
+ var c = text5.charCodeAt(i++);
21931
21931
  if (c === 37) {
21932
- result.push(parseInt(text4.substr(i, 2), 16));
21932
+ result.push(parseInt(text5.substr(i, 2), 16));
21933
21933
  i += 2;
21934
21934
  } else {
21935
21935
  result.push(c);
@@ -21960,10 +21960,10 @@ var require_aes_js = __commonJS({
21960
21960
  };
21961
21961
  })();
21962
21962
  var convertHex = /* @__PURE__ */ (function() {
21963
- function toBytes(text4) {
21963
+ function toBytes(text5) {
21964
21964
  var result = [];
21965
- for (var i = 0; i < text4.length; i += 2) {
21966
- result.push(parseInt(text4.substr(i, 2), 16));
21965
+ for (var i = 0; i < text5.length; i += 2) {
21966
+ result.push(parseInt(text5.substr(i, 2), 16));
21967
21967
  }
21968
21968
  return result;
21969
21969
  }
@@ -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(trim8);
32140
+ protocol = protocol.split(",").map(trim9);
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 trim8(str) {
32209
+ function trim9(str) {
32210
32210
  return str.trim();
32211
32211
  }
32212
32212
  }
@@ -36406,15 +36406,15 @@ var SETTINGS = Object.freeze({
36406
36406
  var MAX_LABEL_LENGTH = 80;
36407
36407
  var trim = (value) => String(value ?? "").trim();
36408
36408
  var normalizeLabel = (value, field) => {
36409
- const text4 = trim(value).replace(/\s+/g, " ");
36410
- if (text4.length > MAX_LABEL_LENGTH) throw new Error(`${field} must be ${MAX_LABEL_LENGTH} characters or fewer.`);
36411
- return text4;
36409
+ const text5 = trim(value).replace(/\s+/g, " ");
36410
+ if (text5.length > MAX_LABEL_LENGTH) throw new Error(`${field} must be ${MAX_LABEL_LENGTH} characters or fewer.`);
36411
+ return text5;
36412
36412
  };
36413
36413
  var normalizeHttpOrigin = (value, field) => {
36414
- const text4 = trim(value);
36414
+ const text5 = trim(value);
36415
36415
  let parsed;
36416
36416
  try {
36417
- parsed = new URL(text4);
36417
+ parsed = new URL(text5);
36418
36418
  } catch {
36419
36419
  throw new Error(`${field} must be an http(s) origin.`);
36420
36420
  }
@@ -36425,11 +36425,11 @@ var normalizeHttpOrigin = (value, field) => {
36425
36425
  };
36426
36426
  var isLocalUrl = (url) => ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
36427
36427
  var normalizeRpcUrl = (value, allowLocal) => {
36428
- const text4 = trim(value);
36429
- if (!text4) return "";
36428
+ const text5 = trim(value);
36429
+ if (!text5) return "";
36430
36430
  let parsed;
36431
36431
  try {
36432
- parsed = new URL(text4);
36432
+ parsed = new URL(text5);
36433
36433
  } catch {
36434
36434
  throw new Error("CE_MCP_RPC_URL must be an https URL.");
36435
36435
  }
@@ -36494,13 +36494,22 @@ var ensureStateDir = async (stateDir) => {
36494
36494
  var writeOwnerOnlyJson = async (filePath, value, { exclusive = false } = {}) => {
36495
36495
  const body = `${JSON.stringify(value, null, 2)}
36496
36496
  `;
36497
- if (exclusive) {
36498
- await fs.writeFile(filePath, body, { mode: OWNER_ONLY_FILE, flag: "wx" });
36499
- return;
36500
- }
36501
36497
  const temporary = `${filePath}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
36502
36498
  await fs.writeFile(temporary, body, { mode: OWNER_ONLY_FILE, flag: "wx" });
36503
- await fs.rename(temporary, filePath);
36499
+ try {
36500
+ if (exclusive) {
36501
+ try {
36502
+ await fs.link(temporary, filePath);
36503
+ } catch (error) {
36504
+ if (!["EPERM", "ENOTSUP", "EXDEV"].includes(error?.code)) throw error;
36505
+ await fs.writeFile(filePath, body, { mode: OWNER_ONLY_FILE, flag: "wx" });
36506
+ }
36507
+ } else {
36508
+ await fs.rename(temporary, filePath);
36509
+ }
36510
+ } finally {
36511
+ await fs.rm(temporary, { force: true });
36512
+ }
36504
36513
  };
36505
36514
  var readKeyFile = async (keyPath) => {
36506
36515
  const info = await fs.lstat(keyPath);
@@ -36631,9 +36640,9 @@ var DIAGNOSTICS = /\[ See: |\((?=\w+=)|\b(?:requestBody|body|url|transaction|dat
36631
36640
  var URLS = /\b[a-z][\w+.-]*:\/\/[^\s"'`]+/gi;
36632
36641
  var hostOnly = (url) => /<\w+>/.test(url) ? url : url.replace(/^([^:]+:\/\/)(?:[^/?#]*@)?([^/?#]*)[\s\S]*$/, "$1$2");
36633
36642
  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.";
36643
+ const text5 = String(error?.message || error || "").slice(0, MAX_ERROR_LENGTH);
36644
+ const cut = text5.search(DIAGNOSTICS);
36645
+ return (cut < 0 ? text5 : text5.slice(0, cut)).trimEnd().replace(URLS, hostOnly) || "Unknown error.";
36637
36646
  };
36638
36647
  var DEFAULT_TOOL_TIMEOUT_MS = 15 * 60 * 1e3;
36639
36648
  var createMcpServer = ({
@@ -36656,8 +36665,8 @@ var createMcpServer = ({
36656
36665
  ...annotations ? { annotations } : {}
36657
36666
  }));
36658
36667
  const toolResult = (value, isError = false) => {
36659
- const text4 = typeof value === "string" ? value : JSON.stringify(value, null, 2);
36660
- const result = { content: [{ type: "text", text: text4 }] };
36668
+ const text5 = typeof value === "string" ? value : JSON.stringify(value, null, 2);
36669
+ const result = { content: [{ type: "text", text: text5 }] };
36661
36670
  if (!isError && isRecord2(value) && protocolVersion >= STRUCTURED_CONTENT_SINCE) {
36662
36671
  result.structuredContent = value;
36663
36672
  }
@@ -36752,11 +36761,11 @@ var createMcpServer = ({
36752
36761
  }
36753
36762
  };
36754
36763
  const handleLine = async (line) => {
36755
- const text4 = String(line || "").trim();
36756
- if (!text4) return "";
36764
+ const text5 = String(line || "").trim();
36765
+ if (!text5) return "";
36757
36766
  let decoded;
36758
36767
  try {
36759
- decoded = JSON.parse(text4);
36768
+ decoded = JSON.parse(text5);
36760
36769
  } catch {
36761
36770
  return JSON.stringify(errorResponse(null, PARSE_ERROR, "Parse error."));
36762
36771
  }
@@ -36905,6 +36914,7 @@ var createTestEthRequester = ({
36905
36914
  paid = await ctx.client.request("/", {
36906
36915
  method: "POST",
36907
36916
  auth: true,
36917
+ signal,
36908
36918
  headers: { "X-Session-Slug": ctx.info.slug },
36909
36919
  body: { action: "request_test_eth", to: wallet.address }
36910
36920
  });
@@ -36961,6 +36971,17 @@ import { createHash, randomUUID as randomUUID2 } from "node:crypto";
36961
36971
  // mcp/lib/registrySession.mjs
36962
36972
  var import_ethers5 = __toESM(require_lib32(), 1);
36963
36973
 
36974
+ // shared/responseReadWindow.mjs
36975
+ var responsePairKey = (responder, id) => `${String(responder || "").toLowerCase()}|${String(id || "").toLowerCase()}`;
36976
+ var responseReadWindowOptions = (window2, responder, id) => window2 ? {
36977
+ _responseReadWindow: window2,
36978
+ blockTag: window2.laterPairs && !window2.laterPairs.has(responsePairKey(responder, id)) ? window2.unchangedBlockTag : window2.blockTag
36979
+ } : {};
36980
+ var historicalResponseReadError = (error, blockTag) => Object.assign(
36981
+ new Error(`Response state at block ${String(blockTag)} is unavailable: ${error instanceof Error ? error.message : String(error)}`),
36982
+ { code: "RESPONSE_HISTORY_UNAVAILABLE", retryable: true, blockTag, cause: error }
36983
+ );
36984
+
36964
36985
  // client/src/variables/contracts.json
36965
36986
  var contracts_default = {
36966
36987
  sessionRegistryAddresses: {
@@ -38202,40 +38223,6 @@ function summarizeQuadraticAllocations(responses, question) {
38202
38223
  return { options, totalResponders, excludedResponses };
38203
38224
  }
38204
38225
 
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
-
38239
38226
  // shared/questions/ratingScale.mjs
38240
38227
  var trim2 = (value) => String(value == null ? "" : value).trim();
38241
38228
  var isObj = (value) => !!value && typeof value === "object" && !Array.isArray(value);
@@ -38290,39 +38277,41 @@ var normalizeRatingScale = (question = {}) => {
38290
38277
  )
38291
38278
  };
38292
38279
  };
38280
+ var DEFAULT_RATING_SCALE = Object.freeze({ min: 0, max: 10, minLabel: "0", maxLabel: "10" });
38281
+ var ratingStepFor = (question = {}) => {
38282
+ const source = [question.scale, question.ratingScale].find((record) => isObj(record) && recordHasRatingScaleMetadata(record)) || question;
38283
+ const step = Number(source.step);
38284
+ return Number.isFinite(step) && step > 0 ? step : 1;
38285
+ };
38286
+ var decimalPlaces = (value) => {
38287
+ const [coefficient, exponent = "0"] = String(value).split("e");
38288
+ return Math.max(0, (coefficient.split(".")[1]?.length || 0) - Number(exponent));
38289
+ };
38290
+ var normalizeRatingAnswer = (value, question = {}) => {
38291
+ if (typeof value !== "number" && typeof value !== "string") return null;
38292
+ if (typeof value === "string" && value.trim() === "") return null;
38293
+ const number = Number(value);
38294
+ const { min, max } = normalizeRatingScale(question) || DEFAULT_RATING_SCALE;
38295
+ const step = ratingStepFor(question);
38296
+ const index = (number - min) / step;
38297
+ if (!Number.isFinite(number) || number < min || number > max || !Number.isFinite(index) || Math.abs(index - Math.round(index)) > 1e-9) return null;
38298
+ const precision = Math.min(100, Math.max(decimalPlaces(step), decimalPlaces(min)));
38299
+ return Number((min + Math.round(index) * step).toFixed(precision));
38300
+ };
38293
38301
 
38294
- // mcp/lib/records.mjs
38302
+ // shared/questions/answerValue.mjs
38295
38303
  var QUESTION_TYPES = Object.freeze(["binary", "rating", "multichoice", "quadratic", "freeform"]);
38296
38304
  var BINARY_ANSWERS = Object.freeze(["Agree", "Unsure", "Disagree"]);
38297
- var MAX_PROMPT_LENGTH = 1e3;
38298
- var MAX_OPTION_LENGTH = 200;
38299
- var MAX_OPTIONS = 12;
38300
- var MAX_TAGS = 10;
38301
- var MAX_TAG_LENGTH = 40;
38302
38305
  var MAX_FREEFORM_LENGTH = 8e3;
38303
- var MAX_COMMENT_LENGTH = 4e3;
38304
38306
  var trim3 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
38305
- var isRecord3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
38306
38307
  var fail = (error) => ({ ok: false, error });
38307
- var { generateQuestionId, questionIdMatchesContent } = createQuestionIdDerivation(import_ethers3.ethers);
38308
- var ratingScaleFor = (question = {}) => normalizeRatingScale(question) || { min: 0, max: 10, minLabel: "0", maxLabel: "10" };
38308
+ var ratingScaleFor = (question = {}) => ({ ...normalizeRatingScale(question) || DEFAULT_RATING_SCALE, step: ratingStepFor(question) });
38309
38309
  var optionsOf = (question) => Array.isArray(question.options) ? question.options.filter((option) => typeof option === "string") : [];
38310
38310
  var isSingleSelect = (question) => question.singleSelect === true || question.oneSelectionOnly === true || question.singleChoice === true;
38311
- var isLockedQuestion = (question) => question?.locked === true || !!question?.promptEncrypted || !!question?.encryption && typeof question.encryption === "object" || typeof question?.prompt === "string" && question.prompt.trim() === "[encrypted]";
38312
- var normalizeRecruitmentSource = (value) => {
38313
- const normalized2 = (typeof value === "string" ? value : "").trim().replace(/\s+/g, "-").slice(0, 128);
38314
- return /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/.test(normalized2) ? normalized2 : "";
38315
- };
38316
38311
  var matchOption = (options, value) => {
38317
38312
  const wanted = trim3(value).toLowerCase();
38318
38313
  return options.find((option) => option.trim().toLowerCase() === wanted);
38319
38314
  };
38320
- var readScore = (value, field) => {
38321
- if (value === void 0 || value === null || value === "") return { ok: true, value: null };
38322
- const number = Number(value);
38323
- if (!Number.isInteger(number) || number < 0 || number > 10) return fail(`${field} must be a whole number from 0 to 10.`);
38324
- return { ok: true, value: number };
38325
- };
38326
38315
  var normalizeAnswerValue = (question = {}, value) => {
38327
38316
  switch (question.type) {
38328
38317
  case "binary": {
@@ -38331,9 +38320,9 @@ var normalizeAnswerValue = (question = {}, value) => {
38331
38320
  }
38332
38321
  case "rating": {
38333
38322
  const scale = ratingScaleFor(question);
38334
- const number = typeof value === "string" && trim3(value) !== "" ? Number(value) : value;
38335
- if (!Number.isInteger(number) || number < scale.min || number > scale.max) {
38336
- return fail(`Rating answers must be a whole number from ${scale.min} to ${scale.max}.`);
38323
+ const number = normalizeRatingAnswer(value, question);
38324
+ if (number === null) {
38325
+ return fail(`Rating answers must be from ${scale.min} to ${scale.max} in increments of ${scale.step} from ${scale.min}.`);
38337
38326
  }
38338
38327
  return { ok: true, value: number };
38339
38328
  }
@@ -38361,15 +38350,72 @@ var normalizeAnswerValue = (question = {}, value) => {
38361
38350
  return { ok: true, value: votes };
38362
38351
  }
38363
38352
  case "freeform": {
38364
- const text4 = trim3(value);
38365
- if (!text4) return fail("Freeform answers must be non-empty text.");
38366
- if (text4.length > MAX_FREEFORM_LENGTH) return fail(`Freeform answers must be ${MAX_FREEFORM_LENGTH} characters or fewer.`);
38367
- return { ok: true, value: text4 };
38353
+ const text5 = trim3(value);
38354
+ if (!text5) return fail("Freeform answers must be non-empty text.");
38355
+ if (text5.length > MAX_FREEFORM_LENGTH) return fail(`Freeform answers must be ${MAX_FREEFORM_LENGTH} characters or fewer.`);
38356
+ return { ok: true, value: text5 };
38368
38357
  }
38369
38358
  default:
38370
38359
  return fail(`Unsupported question type "${trim3(question.type)}".`);
38371
38360
  }
38372
38361
  };
38362
+
38363
+ // shared/questions/questionId.mjs
38364
+ var validOptions = (options) => Array.isArray(options) ? options.filter((o) => o && o.trim() !== "") : [];
38365
+ var legacyPreimage = (type, prompt, options, singleSelect, voiceCredits) => {
38366
+ let preimage = `${type}:${(prompt || "").trim().toLowerCase()}`;
38367
+ const validOpts = validOptions(options);
38368
+ if (type === "multichoice") {
38369
+ if (validOpts.length > 0)
38370
+ preimage += `:${validOpts.map((o) => o.trim()).join(",").toLowerCase()}`;
38371
+ if (singleSelect) preimage += ":single";
38372
+ }
38373
+ if (type === "quadratic") {
38374
+ preimage += `:${JSON.stringify(validOpts.map((option) => option.trim().toLowerCase()))}:${voiceCredits}`;
38375
+ }
38376
+ return preimage;
38377
+ };
38378
+ var legacyIsAmbiguous = (type, options, singleSelect) => {
38379
+ if (type !== "multichoice") return false;
38380
+ const labels = validOptions(options).map((o) => o.trim().toLowerCase());
38381
+ return labels.length === 0 || labels.some((label) => label.includes(":") || label.includes(",")) || !singleSelect && labels.length === 1 && labels[0] === "single";
38382
+ };
38383
+ var v2Text = (text5) => (text5 || "").trim().toLowerCase().normalize("NFC");
38384
+ var v2Preimage = (type, prompt, options, singleSelect) => JSON.stringify(["ce-question-id", 2, type, v2Text(prompt), validOptions(options).map(v2Text), !!singleSelect]);
38385
+ var createQuestionIdDerivation = (ethers7) => {
38386
+ const { id: hash } = ethers7.utils;
38387
+ const generateQuestionId2 = (type, prompt, options = [], singleSelect = false, voiceCredits = 99) => hash(
38388
+ legacyIsAmbiguous(type, options, singleSelect) ? v2Preimage(type, prompt, options, singleSelect) : legacyPreimage(type, prompt, options, singleSelect, voiceCredits)
38389
+ );
38390
+ const questionIdMatchesContent2 = (id, { type, prompt, options = [], singleSelect = false, voiceCredits = 99 } = {}) => {
38391
+ const wanted = String(id).toLowerCase();
38392
+ return hash(legacyPreimage(type, prompt, options, singleSelect, voiceCredits)) === wanted || type === "multichoice" && hash(v2Preimage(type, prompt, options, singleSelect)) === wanted;
38393
+ };
38394
+ return Object.freeze({ generateQuestionId: generateQuestionId2, questionIdMatchesContent: questionIdMatchesContent2 });
38395
+ };
38396
+
38397
+ // mcp/lib/records.mjs
38398
+ var MAX_PROMPT_LENGTH = 1e3;
38399
+ var MAX_OPTION_LENGTH = 200;
38400
+ var MAX_OPTIONS = 12;
38401
+ var MAX_TAGS = 10;
38402
+ var MAX_TAG_LENGTH = 40;
38403
+ var MAX_COMMENT_LENGTH = 4e3;
38404
+ var trim4 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
38405
+ var isRecord3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
38406
+ var fail2 = (error) => ({ ok: false, error });
38407
+ var { generateQuestionId, questionIdMatchesContent } = createQuestionIdDerivation(import_ethers3.ethers);
38408
+ var isLockedQuestion = (question) => question?.locked === true || !!question?.promptEncrypted || !!question?.encryption && typeof question.encryption === "object" || typeof question?.prompt === "string" && question.prompt.trim() === "[encrypted]";
38409
+ var normalizeRecruitmentSource = (value) => {
38410
+ const normalized2 = (typeof value === "string" ? value : "").trim().replace(/\s+/g, "-").slice(0, 128);
38411
+ return /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/.test(normalized2) ? normalized2 : "";
38412
+ };
38413
+ var readScore = (value, field) => {
38414
+ if (value === void 0 || value === null || value === "") return { ok: true, value: null };
38415
+ const number = Number(value);
38416
+ if (!Number.isInteger(number) || number < 0 || number > 10) return fail2(`${field} must be a whole number from 0 to 10.`);
38417
+ return { ok: true, value: number };
38418
+ };
38373
38419
  var plaintextField = (value, hash) => ({
38374
38420
  value,
38375
38421
  encrypted: false,
@@ -38382,8 +38428,8 @@ var plaintextField = (value, hash) => ({
38382
38428
  var textHash = (value) => typeof value === "string" && value ? import_ethers3.ethers.utils.id(value) : "";
38383
38429
  var buildParticipantMarker = ({ modelId = "", platform = "" } = {}) => ({
38384
38430
  kind: "agent",
38385
- ...trim3(modelId) ? { modelId: trim3(modelId) } : {},
38386
- ...trim3(platform) ? { platform: trim3(platform) } : {},
38431
+ ...trim4(modelId) ? { modelId: trim4(modelId) } : {},
38432
+ ...trim4(platform) ? { platform: trim4(platform) } : {},
38387
38433
  verification: "self_reported"
38388
38434
  });
38389
38435
  var buildResponseRecord = ({
@@ -38397,21 +38443,21 @@ var buildResponseRecord = ({
38397
38443
  profile = {},
38398
38444
  recruitmentSource = ""
38399
38445
  }) => {
38400
- if (!isRecord3(question) || !trim3(question.id)) return fail("Unknown question.");
38401
- if (isLockedQuestion(question)) return fail("This question is locked to certain members; agents cannot answer locked questions yet.");
38446
+ if (!isRecord3(question) || !trim4(question.id)) return fail2("Unknown question.");
38447
+ if (isLockedQuestion(question)) return fail2("This question is locked to certain members; agents cannot answer locked questions yet.");
38402
38448
  const answer = normalizeAnswerValue(question, value);
38403
38449
  if (!answer.ok) return answer;
38404
- const note = trim3(comment);
38405
- if (note.length > MAX_COMMENT_LENGTH) return fail(`Comments must be ${MAX_COMMENT_LENGTH} characters or fewer.`);
38450
+ const note = trim4(comment);
38451
+ if (note.length > MAX_COMMENT_LENGTH) return fail2(`Comments must be ${MAX_COMMENT_LENGTH} characters or fewer.`);
38406
38452
  const convictionScore = readScore(conviction, "Conviction");
38407
38453
  if (!convictionScore.ok) return convictionScore;
38408
38454
  const importanceScore = readScore(importance, "Importance");
38409
38455
  if (!importanceScore.ok) return importanceScore;
38410
- const displayName = trim3(profile.displayName);
38456
+ const displayName = trim4(profile.displayName);
38411
38457
  return {
38412
38458
  ok: true,
38413
38459
  record: {
38414
- questionID: trim3(question.id).toLowerCase(),
38460
+ questionID: trim4(question.id).toLowerCase(),
38415
38461
  responder: import_ethers3.ethers.utils.getAddress(responder),
38416
38462
  type: question.type,
38417
38463
  prompt: typeof question.prompt === "string" ? question.prompt : "",
@@ -38429,19 +38475,19 @@ var buildResponseRecord = ({
38429
38475
  };
38430
38476
  };
38431
38477
  var normalizeOptionList = (options) => {
38432
- const list = (Array.isArray(options) ? options : []).map(trim3).filter(Boolean);
38433
- if (list.length < 2) return fail("Provide at least two options.");
38434
- if (list.length > MAX_OPTIONS) return fail(`Provide at most ${MAX_OPTIONS} options.`);
38435
- if (list.some((option) => option.length > MAX_OPTION_LENGTH)) return fail(`Options must be ${MAX_OPTION_LENGTH} characters or fewer.`);
38436
- if (new Set(list.map((option) => option.toLowerCase())).size !== list.length) return fail("Options must be unique.");
38478
+ const list = (Array.isArray(options) ? options : []).map(trim4).filter(Boolean);
38479
+ if (list.length < 2) return fail2("Provide at least two options.");
38480
+ if (list.length > MAX_OPTIONS) return fail2(`Provide at most ${MAX_OPTIONS} options.`);
38481
+ if (list.some((option) => option.length > MAX_OPTION_LENGTH)) return fail2(`Options must be ${MAX_OPTION_LENGTH} characters or fewer.`);
38482
+ if (new Set(list.map((option) => option.toLowerCase())).size !== list.length) return fail2("Options must be unique.");
38437
38483
  return { ok: true, value: list };
38438
38484
  };
38439
38485
  var buildQuestionRecord = ({ type = "binary", prompt, options, singleSelect = false, voiceCredits, tags = [], creator, session, profile = {} }) => {
38440
- if (!QUESTION_TYPES.includes(type)) return fail(`Question type must be one of: ${QUESTION_TYPES.join(", ")}.`);
38441
- const text4 = trim3(prompt);
38442
- if (!text4) return fail("The statement text is empty.");
38443
- if (text4.length > MAX_PROMPT_LENGTH) return fail(`Statements must be ${MAX_PROMPT_LENGTH} characters or fewer.`);
38444
- const record = { id: "", type, prompt: text4 };
38486
+ if (!QUESTION_TYPES.includes(type)) return fail2(`Question type must be one of: ${QUESTION_TYPES.join(", ")}.`);
38487
+ const text5 = trim4(prompt);
38488
+ if (!text5) return fail2("The statement text is empty.");
38489
+ if (text5.length > MAX_PROMPT_LENGTH) return fail2(`Statements must be ${MAX_PROMPT_LENGTH} characters or fewer.`);
38490
+ const record = { id: "", type, prompt: text5 };
38445
38491
  let optionList = [];
38446
38492
  if (type === "multichoice" || type === "quadratic") {
38447
38493
  const normalized2 = normalizeOptionList(options);
@@ -38455,20 +38501,20 @@ var buildQuestionRecord = ({ type = "binary", prompt, options, singleSelect = fa
38455
38501
  credits = voiceCredits === void 0 || voiceCredits === null ? 99 : Number(voiceCredits);
38456
38502
  record.voiceCredits = credits;
38457
38503
  const error = validateQuadraticQuestion(record);
38458
- if (error) return fail(error);
38504
+ if (error) return fail2(error);
38459
38505
  }
38460
- const tagList = (Array.isArray(tags) ? tags : []).map(trim3).filter(Boolean);
38506
+ const tagList = (Array.isArray(tags) ? tags : []).map(trim4).filter(Boolean);
38461
38507
  if (tagList.length > MAX_TAGS || tagList.some((tag) => tag.length > MAX_TAG_LENGTH)) {
38462
- return fail(`Use at most ${MAX_TAGS} tags of ${MAX_TAG_LENGTH} characters or fewer.`);
38508
+ return fail2(`Use at most ${MAX_TAGS} tags of ${MAX_TAG_LENGTH} characters or fewer.`);
38463
38509
  }
38464
38510
  record.tags = tagList;
38465
38511
  record.creator = import_ethers3.ethers.utils.getAddress(creator);
38466
38512
  record.associatedSurveyId = import_ethers3.ethers.constants.HashZero;
38467
38513
  record.participant = buildParticipantMarker(profile);
38468
- record.sessionName = trim3(session.sessionName) || session.sessionSlug;
38514
+ record.sessionName = trim4(session.sessionName) || session.sessionSlug;
38469
38515
  record.sessionSlug = session.sessionSlug;
38470
38516
  record.sessionId = session.sessionId;
38471
- record.id = generateQuestionId(type, text4, optionList, record.singleSelect === true, credits);
38517
+ record.id = generateQuestionId(type, text5, optionList, record.singleSelect === true, credits);
38472
38518
  return { ok: true, id: record.id, record };
38473
38519
  };
38474
38520
 
@@ -38476,9 +38522,9 @@ var buildQuestionRecord = ({ type = "binary", prompt, options, singleSelect = fa
38476
38522
  var SLUG_RE = /^[a-z0-9_-]{1,128}$/;
38477
38523
  var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
38478
38524
  var GROUP_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
38479
- var trim4 = (value) => String(value ?? "").trim();
38525
+ var trim5 = (value) => String(value ?? "").trim();
38480
38526
  var normalizeSessionSlug = (value) => {
38481
- const slug = trim4(value).toLowerCase();
38527
+ const slug = trim5(value).toLowerCase();
38482
38528
  if (!SLUG_RE.test(slug)) throw new Error("Session slug must be 1-128 lowercase letters, digits, dashes, or underscores.");
38483
38529
  return slug;
38484
38530
  };
@@ -38501,9 +38547,9 @@ var readClaimPairs = (params) => {
38501
38547
  const pairs = [];
38502
38548
  const seen = /* @__PURE__ */ new Set();
38503
38549
  const add = (sbt, gp, inv, auto) => {
38504
- const address = trim4(sbt);
38550
+ const address = trim5(sbt);
38505
38551
  if (!ADDRESS_RE.test(address)) return;
38506
- const pair = { sbt: address, groupPassword: trim4(gp), inviteCode: trim4(inv), auto };
38552
+ const pair = { sbt: address, groupPassword: trim5(gp), inviteCode: trim5(inv), auto };
38507
38553
  const key = `${address.toLowerCase()}|${pair.groupPassword}|${pair.inviteCode}`;
38508
38554
  if (seen.has(key)) return;
38509
38555
  seen.add(key);
@@ -38521,7 +38567,7 @@ var readClaimPairs = (params) => {
38521
38567
  var parseSessionLink = (link, { allowLocalWorkers = false } = {}) => {
38522
38568
  let url;
38523
38569
  try {
38524
- url = new URL(trim4(link));
38570
+ url = new URL(trim5(link));
38525
38571
  } catch {
38526
38572
  throw new Error("Session link must be a full URL, such as https://contextengine.sh/session/<slug>?worker=<origin>.");
38527
38573
  }
@@ -38534,7 +38580,7 @@ var parseSessionLink = (link, { allowLocalWorkers = false } = {}) => {
38534
38580
  if (workers.length > 1) {
38535
38581
  throw new Error("Session link must name exactly one hosted Worker when using ?worker=.");
38536
38582
  }
38537
- const joinGroup = trim4(params.get("joinGroup"));
38583
+ const joinGroup = trim5(params.get("joinGroup"));
38538
38584
  if (joinGroup && !GROUP_ID_RE.test(joinGroup)) throw new Error("Session link has an invalid group id.");
38539
38585
  return {
38540
38586
  appOrigin: normalizeHttpOrigin(url.origin, "Session link"),
@@ -38710,8 +38756,13 @@ var createWorkerClient = ({ workerOrigin, slug, trustedOrigins, wallet, fetchImp
38710
38756
  let pendingLogin = null;
38711
38757
  let presence = null;
38712
38758
  let presenceLoadedAt = -Infinity;
38713
- const request = async (path3, { method = "GET", headers = {}, body, auth = false, retried = false, maxBytes = MAX_RESPONSE_BYTES } = {}) => {
38759
+ const request = async (path3, { method = "GET", headers = {}, body, auth = false, retried = false, maxBytes = MAX_RESPONSE_BYTES, signal } = {}) => {
38760
+ const checkpoint = () => {
38761
+ if (signal?.aborted) throw new Error("Stopped before the Worker request was sent.");
38762
+ };
38763
+ checkpoint();
38714
38764
  if (auth) await ensureToken();
38765
+ checkpoint();
38715
38766
  let response;
38716
38767
  try {
38717
38768
  response = await fetchImpl(`${workerOrigin}${path3}`, {
@@ -38741,7 +38792,7 @@ var createWorkerClient = ({ workerOrigin, slug, trustedOrigins, wallet, fetchImp
38741
38792
  const error = isRecord4(data) ? text(data.error) || text(data.message) : "";
38742
38793
  if (response.status === 401 && auth && !retried && (!error || TOKEN_ERROR_RE.test(error))) {
38743
38794
  token = "";
38744
- return request(path3, { method, headers, body, auth, retried: true, maxBytes });
38795
+ return request(path3, { method, headers, body, auth, retried: true, maxBytes, signal });
38745
38796
  }
38746
38797
  if (response.status === 401 && error && !TOKEN_ERROR_RE.test(error)) {
38747
38798
  throw new WorkerRequestError(error.slice(0, MAX_ERROR_DETAIL), { status: 401, code: error });
@@ -38859,15 +38910,16 @@ var createWorkerClient = ({ workerOrigin, slug, trustedOrigins, wallet, fetchImp
38859
38910
  }
38860
38911
  return data;
38861
38912
  };
38862
- const upload = async ({ resource, data, tags = [], requestId = "" }) => {
38913
+ const upload = async ({ resource, data, tags = [], requestId = "", signal }) => {
38863
38914
  const result = await request("/storage/upload", {
38915
+ signal,
38864
38916
  method: "POST",
38865
38917
  auth: true,
38866
38918
  body: { backend: "cloudflare", resource, contentType: "application/json", payloadEncrypted: false, tags, data, ...requestId ? { requestId } : {} }
38867
38919
  });
38868
38920
  return isRecord4(result?.storageRef) ? result.storageRef : { id: text(result?.id) };
38869
38921
  };
38870
- const joinGroup = (groupId) => request("/groups/join", { method: "POST", auth: true, body: { sessionId, groupId } });
38922
+ const joinGroup = (groupId, { signal } = {}) => request("/groups/join", { method: "POST", auth: true, signal, body: { sessionId, groupId } });
38871
38923
  const describeTestEth = async () => {
38872
38924
  await refreshConfig();
38873
38925
  if (now() - presenceLoadedAt >= CONFIG_MAX_AGE_MS) {
@@ -39101,6 +39153,7 @@ var createRegistryWriter = ({ provider, chainId, wallet, surveys, slug, remember
39101
39153
  if (offer.offered && offer.tokenHasFaucetScope !== false) {
39102
39154
  let funding;
39103
39155
  try {
39156
+ stopped(signal);
39104
39157
  funding = await fund();
39105
39158
  } catch (error) {
39106
39159
  throw new Error(`${shortfall} ${String(error?.message || "The faucet request failed.").slice(0, 250)}`);
@@ -39117,7 +39170,7 @@ var createRegistryWriter = ({ provider, chainId, wallet, surveys, slug, remember
39117
39170
  };
39118
39171
  const upload = async (client, resource, id, record, signal) => {
39119
39172
  stopped(signal);
39120
- const result = await client.request("/arweave/upload", { method: "POST", auth: true, body: {
39173
+ const result = await client.request("/arweave/upload", { method: "POST", auth: true, signal, body: {
39121
39174
  data: JSON.stringify(record),
39122
39175
  contentType: "application/json",
39123
39176
  requestId: randomUUID(),
@@ -39163,6 +39216,7 @@ var createRegistryWriter = ({ provider, chainId, wallet, surveys, slug, remember
39163
39216
  for (const record of records) pointers.push(await upload(client, "responses", record.questionID, record, signal));
39164
39217
  stopped(signal);
39165
39218
  await checkChain();
39219
+ stopped(signal);
39166
39220
  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 });
39167
39221
  const receipt = await confirm(tx, "ResponsesSubmitted", ids);
39168
39222
  records.forEach((record, index) => remember("responses", record.questionID, record, pointers[index], receipt));
@@ -39177,6 +39231,7 @@ var createRegistryWriter = ({ provider, chainId, wallet, surveys, slug, remember
39177
39231
  const pointer = await upload(client, "questions", record.id, record, signal);
39178
39232
  stopped(signal);
39179
39233
  await checkChain();
39234
+ stopped(signal);
39180
39235
  const tx = await chainRequest(() => surveys.connect(wallet.connect(provider)).addQuestions([record.id], [pointer], [import_ethers4.ethers.constants.HashZero], fees), { submission: true });
39181
39236
  const receipt = await confirm(tx, "QuestionsAdded", [record.id]);
39182
39237
  remember("questions", record.id, record, pointer, receipt);
@@ -39240,6 +39295,11 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39240
39295
  let workerOrigin;
39241
39296
  let surveys;
39242
39297
  let startBlock;
39298
+ const creationBlocks = /* @__PURE__ */ new Map();
39299
+ let endBlock = null;
39300
+ let readEnd;
39301
+ let windowKey;
39302
+ let questionMark = null;
39243
39303
  let questionCursor;
39244
39304
  const pending = /* @__PURE__ */ new Map();
39245
39305
  const questions = /* @__PURE__ */ new Map();
@@ -39247,9 +39307,23 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39247
39307
  const localAnswers = /* @__PURE__ */ new Map();
39248
39308
  const answerScans = /* @__PURE__ */ new Map();
39249
39309
  let loadingQuestions;
39310
+ const containsBlock = (block) => Number.isSafeInteger(block) && block >= startBlock && (endBlock === null || block <= readEnd);
39311
+ const readWindowEnd = async () => {
39312
+ const head = await chainRequest(() => provider.getBlockNumber());
39313
+ readEnd = endBlock === null ? head : Math.min(endBlock, head);
39314
+ return readEnd;
39315
+ };
39316
+ const configuredBlock = (value, name) => {
39317
+ if (value == null) return null;
39318
+ const block = Number(value);
39319
+ if (typeof value !== "number" && typeof value !== "string" || String(value).trim() === "" || !Number.isSafeInteger(block) || block < 0) {
39320
+ throw new Error(`Invalid session block limit: ${name} must be a nonnegative integer.`);
39321
+ }
39322
+ return block;
39323
+ };
39250
39324
  const loadConfig = async () => {
39251
39325
  await checkRegistryChain(provider, chainId);
39252
- const slug = info.slug === "general" ? "" : info.slug;
39326
+ const slug = info.slug || "general";
39253
39327
  const row = await chainRequest(() => registry.getSessionBySlug(slug));
39254
39328
  if (normalized(row[0]) !== slug) throw new Error("The registry returned a different session.");
39255
39329
  if (text(row[3])) throw new Error("Encrypted registry metadata is unsupported; use an unencrypted session.");
@@ -39277,11 +39351,32 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39277
39351
  corsWorkerUrl: workerOrigin,
39278
39352
  contracts: { ...metadata.contracts, surveys: { address, chainId } }
39279
39353
  };
39280
- if (startBlock === void 0) {
39281
- const configured = Number(metadata.blockLimits?.start);
39282
- const head = await chainRequest(() => provider.getBlockNumber());
39283
- startBlock = metadata.blockLimits?.start !== void 0 && Number.isSafeInteger(configured) && configured >= 0 ? configured : await findStartBlock(provider, Number(row[5]), head);
39354
+ const head = await chainRequest(() => provider.getBlockNumber());
39355
+ let nextStart = configuredBlock(metadata.blockLimits?.start, "start");
39356
+ if (nextStart === null) {
39357
+ const createdAt = Number(row[5]);
39358
+ if (!creationBlocks.has(createdAt)) {
39359
+ const lookup = findStartBlock(provider, createdAt, head).catch((error) => {
39360
+ creationBlocks.delete(createdAt);
39361
+ throw error;
39362
+ });
39363
+ creationBlocks.set(createdAt, lookup);
39364
+ }
39365
+ nextStart = await creationBlocks.get(createdAt);
39366
+ }
39367
+ const nextEnd = configuredBlock(metadata.blockLimits?.end, "end");
39368
+ if (nextEnd !== null && nextEnd < nextStart) throw new Error("Invalid session block window: end must not precede start.");
39369
+ const nextKey = `${nextStart}:${nextEnd ?? "head"}`;
39370
+ startBlock = nextStart;
39371
+ endBlock = nextEnd;
39372
+ readEnd = endBlock === null ? head : Math.min(endBlock, head);
39373
+ if (windowKey !== nextKey) {
39374
+ windowKey = nextKey;
39284
39375
  questionCursor = { block: startBlock, offset: 0 };
39376
+ questionMark = null;
39377
+ pending.clear();
39378
+ for (const [id, entry] of questions) if (!entry.local || !containsBlock(entry.blockNumber)) questions.delete(id);
39379
+ answerScans.clear();
39285
39380
  }
39286
39381
  return sessionConfig;
39287
39382
  };
@@ -39311,14 +39406,14 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39311
39406
  return block?.hash ? block.hash !== mark.hash : null;
39312
39407
  };
39313
39408
  const replacedWrite = (entry, blocks = /* @__PURE__ */ new Map()) => historyReplaced(entry?.blockHash ? { number: entry.blockNumber, hash: entry.blockHash } : null, blocks);
39314
- let questionMark = null;
39315
39409
  const visibleQuestions = () => {
39316
39410
  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));
39411
+ return [...questions.values()].filter((entry) => !blocked.has(entry.id) && containsBlock(entry.blockNumber));
39318
39412
  };
39319
39413
  const readQuestions = async ({ signal } = {}) => {
39320
39414
  const deadline = now() + 9e4;
39321
39415
  const blocks = /* @__PURE__ */ new Map();
39416
+ const end = await readWindowEnd();
39322
39417
  let complete = true;
39323
39418
  const replaced = await historyReplaced(questionMark, blocks);
39324
39419
  if (replaced === null) return { items: visibleQuestions(), complete: false };
@@ -39335,7 +39430,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39335
39430
  if (replaced2) questions.delete(id);
39336
39431
  }
39337
39432
  if (pending.size < MAX_CACHE && questions.size < MAX_QUESTIONS) {
39338
- const head = await readHeadMark(await chainRequest(() => provider.getBlockNumber()), blocks);
39433
+ const head = await readHeadMark(end, blocks);
39339
39434
  if (head) {
39340
39435
  const scanned = await scanRegistryLogs({
39341
39436
  provider,
@@ -39389,7 +39484,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39389
39484
  let verified = true;
39390
39485
  const responseFilter = filter("ResponsesSubmitted");
39391
39486
  if (mine) responseFilter.topics.push(import_ethers5.ethers.utils.hexZeroPad(wallet.address, 32));
39392
- const head = await chainRequest(() => provider.getBlockNumber());
39487
+ const head = await readWindowEnd();
39393
39488
  const scope = [...known].sort().join(",");
39394
39489
  let state = answerScans.get(mine);
39395
39490
  const replaced = !!state && state.scope === scope && head >= state.head && await historyReplaced(state.mark, blocks);
@@ -39397,7 +39492,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39397
39492
  const held = new Map((state.result?.entries || []).map((entry) => [`${entry.questionId}:${normalized(entry.responder)}`, entry]));
39398
39493
  const chainOrders2 = new Map(state.result?.chainOrders || []);
39399
39494
  for (const entry of localAnswers.values()) {
39400
- if (!known.has(entry.questionId)) continue;
39495
+ if (!known.has(entry.questionId) || !containsBlock(entry.blockNumber)) continue;
39401
39496
  const key = `${entry.questionId}:${normalized(entry.responder)}`;
39402
39497
  if ([state.latest.get(key), held.get(key)].some((previous) => previous && compareChainOrder(entry, previous) < 0)) continue;
39403
39498
  chainOrders2.set(entry.questionId, entry);
@@ -39407,7 +39502,8 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39407
39502
  return {
39408
39503
  entries: [...held.values()].sort((a, b) => compareChainOrder(b, a)).slice(0, maxRecords),
39409
39504
  complete: false,
39410
- chainOrders: chainOrders2
39505
+ chainOrders: chainOrders2,
39506
+ unreadAnswers: state.result?.unreadAnswers || 0
39411
39507
  };
39412
39508
  }
39413
39509
  if (!state || state.scope !== scope || head < state.head || replaced) {
@@ -39473,7 +39569,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39473
39569
  const scannedAll = !state.ranges.length && !state.capped;
39474
39570
  const latest = new Map(state.latest);
39475
39571
  for (const [id, entry] of localAnswers) {
39476
- if (!known.has(entry.questionId)) continue;
39572
+ if (!known.has(entry.questionId) || !containsBlock(entry.blockNumber)) continue;
39477
39573
  const replaced2 = await replacedWrite(entry, blocks);
39478
39574
  if (replaced2 === null) verified = false;
39479
39575
  if (replaced2) {
@@ -39488,6 +39584,7 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39488
39584
  const chainOrders = new Map(candidates.filter((entry) => normalized(entry.responder) === normalized(wallet.address)).map((entry) => [entry.questionId, entry]));
39489
39585
  const entries = [];
39490
39586
  let complete = verified && scannedAll && candidates.length <= maxRecords;
39587
+ let unreadAnswers = 0;
39491
39588
  let next = 0;
39492
39589
  const selected = candidates.slice(0, maxRecords);
39493
39590
  await Promise.all(Array.from({ length: Math.min(6, selected.length) }, async () => {
@@ -39498,7 +39595,16 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39498
39595
  }
39499
39596
  const entry = selected[next++];
39500
39597
  try {
39501
- const pointer = entry.local ? entry.storageId : await chainRequest(() => surveys.getResponse(entry.responder, entry.questionId));
39598
+ const { blockTag } = responseReadWindowOptions(endBlock === null ? null : { blockTag: head }, entry.responder, entry.questionId);
39599
+ let pointer;
39600
+ if (entry.local) pointer = entry.storageId;
39601
+ else {
39602
+ try {
39603
+ pointer = await chainRequest(() => blockTag == null ? surveys.getResponse(entry.responder, entry.questionId) : surveys.getResponse(entry.responder, entry.questionId, { blockTag }));
39604
+ } catch (error) {
39605
+ throw blockTag == null ? error : historicalResponseReadError(error, blockTag);
39606
+ }
39607
+ }
39502
39608
  const payload = await readPayload(pointer);
39503
39609
  if (!registryResponseMatchesSession(payload, sessionConfig) || payload.answer?.encrypted === true) continue;
39504
39610
  if (normalized(payload.questionID || payload.questionId) !== entry.questionId) continue;
@@ -39508,10 +39614,12 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39508
39614
  entries.push({ ...entry, storageId: pointer, createdAt, payload });
39509
39615
  } catch {
39510
39616
  complete = false;
39617
+ unreadAnswers++;
39511
39618
  }
39512
39619
  }
39513
39620
  }));
39514
- state.result = { entries, complete, chainOrders };
39621
+ unreadAnswers += selected.length - next;
39622
+ state.result = { entries, complete, chainOrders, unreadAnswers };
39515
39623
  return state.result;
39516
39624
  };
39517
39625
  const writer = createRegistryWriter({ provider, chainId, wallet, surveys, slug: info.slug, remember: (resource, id, payload, pointer, receipt) => {
@@ -39531,15 +39639,17 @@ var createRegistrySession = async ({ info, config, wallet, fetchImpl = globalThi
39531
39639
  if (resource === "questions") questions.set(id, { ...entry, local: true });
39532
39640
  else localAnswers.set(id, entry);
39533
39641
  } });
39534
- return { loadConfig, loadQuestions, readAnswers, replacedWrite, ...writer, get config() {
39642
+ return { loadConfig, loadQuestions, readAnswers, replacedWrite, containsBlock, get windowKey() {
39643
+ return windowKey;
39644
+ }, ...writer, get config() {
39535
39645
  return sessionConfig;
39536
39646
  }, workerOrigin };
39537
39647
  };
39538
39648
 
39539
39649
  // shared/storage/canonicalStorageVersions.mjs
39540
- var trim5 = (value) => (typeof value === "string" ? value : value == null ? "" : String(value)).trim();
39650
+ var trim6 = (value) => (typeof value === "string" ? value : value == null ? "" : String(value)).trim();
39541
39651
  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));
39652
+ var compareVersionAge = (left, right) => (Number(left.createdAtMs) || 0) - (Number(right.createdAtMs) || 0) || compareStorageIds(trim6(left.storageRefId), trim6(right.storageRefId));
39543
39653
  var isAdminStorageVersion = (candidate) => candidate.uploadAuthority === "admin";
39544
39654
  var isPreferredStorageVersion = (candidate, current) => {
39545
39655
  if (!current) return true;
@@ -39550,8 +39660,46 @@ var isPreferredStorageVersion = (candidate, current) => {
39550
39660
  return candidateAdmin ? age > 0 : age < 0;
39551
39661
  };
39552
39662
 
39553
- // mcp/lib/results.mjs
39663
+ // shared/sessionContext.mjs
39664
+ var SESSION_CONTEXT_LIMITS = Object.freeze({
39665
+ titleChars: 80,
39666
+ paragraphs: 6,
39667
+ paragraphChars: 2e3,
39668
+ links: 8,
39669
+ labelChars: 120,
39670
+ urlChars: 2e3,
39671
+ htmlChars: 2e4
39672
+ });
39673
+ var L = SESSION_CONTEXT_LIMITS;
39674
+ var isRecord5 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
39554
39675
  var text2 = (value) => typeof value === "string" ? value.trim() : "";
39676
+ var NAMED_ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " " };
39677
+ var decodeEntity = (match, entity) => {
39678
+ const code = /^#x/i.test(entity) ? parseInt(entity.slice(2), 16) : /^#/.test(entity) ? parseInt(entity.slice(1), 10) : NaN;
39679
+ if (Number.isInteger(code)) return code > 0 && code <= 1114111 ? String.fromCodePoint(code) : " ";
39680
+ return NAMED_ENTITIES[entity.toLowerCase()] ?? match;
39681
+ };
39682
+ var htmlToPlainText = (html) => String(html ?? "").replace(/<(script|style|template)\b[\s\S]*?<\/\1\s*>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<br\s*\/?>|<\/(?:p|div|li|h[1-6]|tr|summary|details|blockquote|pre|table|ul|ol|dt|dd)>/gi, "\n").replace(/<[^>]*>/g, " ").replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, decodeEntity).split("\n").map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
39683
+ var isHttpsUrl = (value) => {
39684
+ if (typeof value !== "string" || !value || value.length > L.urlChars) return false;
39685
+ try {
39686
+ return new URL(value).protocol === "https:";
39687
+ } catch {
39688
+ return false;
39689
+ }
39690
+ };
39691
+ var normalizeSessionContext = (value) => {
39692
+ const context = isRecord5(value) ? value : {};
39693
+ const title = text2(context.title).slice(0, L.titleChars);
39694
+ const html = text2(context.html).slice(0, L.htmlChars);
39695
+ if (html) return { title, html, paragraphs: [], links: [] };
39696
+ const paragraphs = (Array.isArray(context.paragraphs) ? context.paragraphs : [context.body]).map(text2).filter(Boolean).slice(0, L.paragraphs).map((paragraph) => paragraph.slice(0, L.paragraphChars));
39697
+ const links = (Array.isArray(context.links) ? context.links : []).filter(isRecord5).map((link) => ({ label: text2(link.label).slice(0, L.labelChars), url: text2(link.url) })).filter((link) => isHttpsUrl(link.url)).slice(0, L.links).map((link) => ({ label: link.label || link.url, url: link.url }));
39698
+ return { title, paragraphs, links };
39699
+ };
39700
+
39701
+ // mcp/lib/results.mjs
39702
+ var text3 = (value) => typeof value === "string" ? value.trim() : "";
39555
39703
  var MAX_FREEFORM_SAMPLES = 5;
39556
39704
  var MAX_SAMPLE_LENGTH = 280;
39557
39705
  var isNewer = (candidate, current) => !current || candidate.createdAt > current.createdAt || candidate.createdAt === current.createdAt && candidate.storageId > current.storageId;
@@ -39560,11 +39708,11 @@ var latestAnswersByQuestion = (entries, { sessionSlug, sessionId, registryConfig
39560
39708
  for (const entry of entries) {
39561
39709
  const payload = entry?.payload;
39562
39710
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) continue;
39563
- const questionId = (text2(payload.questionID) || text2(payload.questionId)).toLowerCase();
39711
+ const questionId = (text3(payload.questionID) || text3(payload.questionId)).toLowerCase();
39564
39712
  if (registryConfig) {
39565
39713
  if (!questionIds?.has(questionId) || !registryResponseMatchesSession(payload, registryConfig) || payload.answer?.encrypted === true) continue;
39566
- } else if (text2(payload.sessionSlug) !== sessionSlug || normalizeSessionIdHex(text2(payload.sessionId)) !== sessionId) continue;
39567
- const responder = text2(entry.responder).toLowerCase();
39714
+ } else if (text3(payload.sessionSlug) !== sessionSlug || normalizeSessionIdHex(text3(payload.sessionId)) !== sessionId) continue;
39715
+ const responder = text3(entry.responder).toLowerCase();
39568
39716
  if (!questionId || !responder) continue;
39569
39717
  const answers = byQuestion.get(questionId) || /* @__PURE__ */ new Map();
39570
39718
  const current = answers.get(responder);
@@ -39596,25 +39744,21 @@ var BINARY_VOTES = Object.freeze({
39596
39744
  0: "Unsure"
39597
39745
  });
39598
39746
  var validValue = (question, entry) => {
39599
- const payloadType = text2(entry?.payload?.type);
39747
+ const payloadType = text3(entry?.payload?.type);
39600
39748
  if (payloadType && payloadType !== question.type) return void 0;
39601
39749
  const value = rawValue(entry);
39602
39750
  if (value === void 0 || value === null || value === "*") return void 0;
39603
39751
  switch (question.type) {
39604
39752
  case "binary": {
39605
39753
  const scalar = Array.isArray(value) && value.every((item) => ["string", "number"].includes(typeof item)) ? value.join(",") : value;
39606
- const key = typeof scalar === "boolean" || typeof scalar === "number" ? String(scalar) : text2(scalar).toLowerCase();
39754
+ const key = typeof scalar === "boolean" || typeof scalar === "number" ? String(scalar) : text3(scalar).toLowerCase();
39607
39755
  return Object.hasOwn(BINARY_VOTES, key) ? BINARY_VOTES[key] : void 0;
39608
39756
  }
39609
- case "rating": {
39610
- if (typeof value !== "number" && !(typeof value === "string" && value.trim())) return void 0;
39611
- const number = Number(value);
39612
- const scale = ratingScaleFor(question);
39613
- return Number.isFinite(number) && number >= scale.min && number <= scale.max ? number : void 0;
39614
- }
39757
+ case "rating":
39758
+ return normalizeRatingAnswer(value, question) ?? void 0;
39615
39759
  case "multichoice": {
39616
- const options = (Array.isArray(question.options) ? question.options : []).map(text2).filter(Boolean);
39617
- const picks = (Array.isArray(value) ? value : [value]).map(text2);
39760
+ const options = (Array.isArray(question.options) ? question.options : []).map(text3).filter(Boolean);
39761
+ const picks = (Array.isArray(value) ? value : [value]).map(text3);
39618
39762
  if (!picks.length || picks.some((pick) => !pick)) return void 0;
39619
39763
  const canonical = options.length ? picks.map((pick) => options.find((option) => option.toLowerCase() === pick.toLowerCase()) || "") : picks;
39620
39764
  if (canonical.some((pick) => !pick)) return void 0;
@@ -39665,7 +39809,7 @@ var summarizeQuestion = (question, answersByResponder = /* @__PURE__ */ new Map(
39665
39809
  return { ...summary, scale: { min: scale.min, max: scale.max }, mean, histogram };
39666
39810
  }
39667
39811
  case "multichoice": {
39668
- const counts = new Map((Array.isArray(question.options) ? question.options : []).map(text2).filter(Boolean).map((option) => [option, 0]));
39812
+ const counts = new Map((Array.isArray(question.options) ? question.options : []).map(text3).filter(Boolean).map((option) => [option, 0]));
39669
39813
  answered.forEach((entry) => validValue(question, entry).forEach((option) => counts.set(option, (counts.get(option) || 0) + 1)));
39670
39814
  return { ...summary, counts: Object.fromEntries(counts) };
39671
39815
  }
@@ -39715,12 +39859,12 @@ var REVEAL_WAIT_MS = 9e4;
39715
39859
  var POLL_MS = 2e3;
39716
39860
  var GAS_HEADROOM_PERCENT = 130;
39717
39861
  var PASSWORD_CLAIM_GAS_BUDGET = 4e5;
39718
- var trim6 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
39719
- var isRecord5 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
39862
+ var trim7 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
39863
+ var isRecord6 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
39720
39864
  var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
39721
39865
  var { deriveGroupPasswordWallet } = createGroupPasswordDerivation(import_ethers6.ethers);
39722
39866
  var normalizeGroupPasswordInput = (raw) => {
39723
- const compact = trim6(raw).replace(/\s+/g, "");
39867
+ const compact = trim7(raw).replace(/\s+/g, "");
39724
39868
  if (!compact) return "";
39725
39869
  if (import_ethers6.ethers.utils.isHexString(compact)) {
39726
39870
  try {
@@ -39732,14 +39876,14 @@ var normalizeGroupPasswordInput = (raw) => {
39732
39876
  return compact;
39733
39877
  };
39734
39878
  var decodeInviteCode = (raw) => {
39735
- let code = trim6(raw);
39879
+ let code = trim7(raw);
39736
39880
  const lower = code.toLowerCase();
39737
39881
  if (lower.startsWith("inv:")) code = code.slice(4).trim();
39738
39882
  else if (lower.startsWith("invite:")) code = code.slice(7).trim();
39739
39883
  if (!code || code.length > 4096) return null;
39740
39884
  try {
39741
39885
  const parsed = JSON.parse(Buffer.from(code.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
39742
- if (!isRecord5(parsed) || Object.keys(parsed).sort().join(",") !== "a,c,n,s") return null;
39886
+ if (!isRecord6(parsed) || Object.keys(parsed).sort().join(",") !== "a,c,n,s") return null;
39743
39887
  const chainId = import_ethers6.ethers.BigNumber.from(String(parsed.c));
39744
39888
  const slot = import_ethers6.ethers.BigNumber.from(String(parsed.n));
39745
39889
  const sbtAddress = import_ethers6.ethers.utils.getAddress(String(parsed.a));
@@ -39760,13 +39904,13 @@ var accessConditionsOf = (config) => [
39760
39904
  config?.sessionModeProfile?.encryption?.accessConditions,
39761
39905
  config?.storageProfile?.payloadAccessControl?.accessConditions
39762
39906
  ].flatMap((document) => Array.isArray(document?.conditions) ? document.conditions : []);
39763
- var conditionAddresses = (condition) => [condition?.contract, condition?.address, ...Array.isArray(condition?.sbtAddresses) ? condition.sbtAddresses : []].map((value) => trim6(value).toLowerCase()).filter(Boolean);
39907
+ var conditionAddresses = (condition) => [condition?.contract, condition?.address, ...Array.isArray(condition?.sbtAddresses) ? condition.sbtAddresses : []].map((value) => trim7(value).toLowerCase()).filter(Boolean);
39764
39908
  var positiveChainId = (value) => {
39765
39909
  const number = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : NaN;
39766
39910
  return Number.isSafeInteger(number) && number > 0 ? number : 0;
39767
39911
  };
39768
39912
  var resolveSbtChainId = (config, sbtAddress) => {
39769
- const target = trim6(sbtAddress).toLowerCase();
39913
+ const target = trim7(sbtAddress).toLowerCase();
39770
39914
  const condition = accessConditionsOf(config).find(
39771
39915
  (entry) => entry?.kind === "sbt_onchain" && conditionAddresses(entry).includes(target) && positiveChainId(entry.chainId)
39772
39916
  );
@@ -39784,7 +39928,7 @@ var listSessionSbts = (config) => {
39784
39928
  return [...seen.values()];
39785
39929
  };
39786
39930
  var defaultCreateProvider = (rpcOverride) => (chainId) => {
39787
- const url = trim6(rpcOverride) || import_rpcDefaults3.default.getPublicRpcUrls(chainId)[0] || import_rpcDefaults3.default.getPathRpcUrl(chainId);
39931
+ const url = trim7(rpcOverride) || import_rpcDefaults3.default.getPublicRpcUrls(chainId)[0] || import_rpcDefaults3.default.getPathRpcUrl(chainId);
39788
39932
  if (!url) throw new Error(`No public RPC is known for chain ${chainId}; set CE_MCP_RPC_URL.`);
39789
39933
  return new import_ethers6.ethers.providers.StaticJsonRpcProvider(url, chainId);
39790
39934
  };
@@ -39903,7 +40047,7 @@ var createSbtClaimer = ({
39903
40047
  const claimWithGroupPassword = async ({ ctx, provider, chainId, contract, sbtAddress, password }) => {
39904
40048
  const normalized2 = normalizeGroupPasswordInput(password);
39905
40049
  if (!normalized2) throw new ClaimError("This SBT needs a group password.");
39906
- const expectedHash = trim6(await contract.groupPasswordHash()).toLowerCase();
40050
+ const expectedHash = trim7(await contract.groupPasswordHash()).toLowerCase();
39907
40051
  if (!expectedHash || expectedHash === import_ethers6.ethers.constants.HashZero) throw new ClaimError("This SBT has no group password set.");
39908
40052
  const groupWallet = await deriveGroupPasswordWallet({ password: normalized2, sbtAddress });
39909
40053
  if (import_ethers6.ethers.utils.solidityKeccak256(["address"], [groupWallet.address]) !== expectedHash) {
@@ -39937,7 +40081,7 @@ var createSbtClaimer = ({
39937
40081
  signal = callSignal;
39938
40082
  let address;
39939
40083
  try {
39940
- address = import_ethers6.ethers.utils.getAddress(trim6(sbtAddress));
40084
+ address = import_ethers6.ethers.utils.getAddress(trim7(sbtAddress));
39941
40085
  } catch {
39942
40086
  throw new ClaimError("Pass the SBT contract address (0x\u2026).");
39943
40087
  }
@@ -39970,7 +40114,7 @@ var createSbtClaimer = ({
39970
40114
  const args = { ctx, provider, chainId, contract, sbtAddress: address };
39971
40115
  let transactions;
39972
40116
  if (mode === "public") transactions = [(await send({ ...args, method: "claim", args: [] })).transactionHash];
39973
- else if (mode === "password") transactions = await claimWithCommitReveal({ ...args, password: trim6(claimPassword) || trim6(groupPassword) });
40117
+ else if (mode === "password") transactions = await claimWithCommitReveal({ ...args, password: trim7(claimPassword) || trim7(groupPassword) });
39974
40118
  else if (mode === "group_password") transactions = await claimWithGroupPassword({ ...args, password: groupPassword });
39975
40119
  else transactions = await claimWithInviteCode({ ...args, code: inviteCode || groupPassword });
39976
40120
  return { ...base, mode, status: "claimed", transactions };
@@ -39996,11 +40140,9 @@ var MAX_CACHED_RECORDS = 2e4;
39996
40140
  var MISSING_RECORD_RETRY_MS = 6e4;
39997
40141
  var MAX_OWN_ANSWER_ITEMS = 5e3;
39998
40142
  var MAX_TITLE_LENGTH = 120;
39999
- var MAX_CONTEXT_PARAGRAPHS = 4;
40000
- var MAX_CONTEXT_LENGTH = 2e3;
40001
- var MAX_CONTEXT_LINKS = 4;
40002
- var trim7 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
40003
- var isRecord6 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
40143
+ var MAX_DESCRIPTION_LENGTH = 2e3;
40144
+ var trim8 = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
40145
+ var isRecord7 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
40004
40146
  var sessionKey = (info) => info.kind === "registry" ? `registry ${info.chainId || 11155420} ${info.slug}` : `${info.workerOrigin} ${info.slug}`;
40005
40147
  var mapLimit = async (items, limit, task) => {
40006
40148
  const results = new Array(items.length);
@@ -40016,10 +40158,10 @@ var mapLimit = async (items, limit, task) => {
40016
40158
  return results;
40017
40159
  };
40018
40160
  var contextOf = (config) => {
40019
- const context = isRecord6(config?.sessionContext) ? config.sessionContext : {};
40020
- const paragraphs = (Array.isArray(context.paragraphs) ? context.paragraphs.map(text) : [text(context.body)]).filter(Boolean).slice(0, MAX_CONTEXT_PARAGRAPHS).map((paragraph) => paragraph.slice(0, MAX_CONTEXT_LENGTH));
40021
- const links = (Array.isArray(context.links) ? context.links : []).filter((link) => isRecord6(link) && /^https?:\/\//i.test(text(link.url))).slice(0, MAX_CONTEXT_LINKS).map((link) => ({ label: (text(link.label) || text(link.url)).slice(0, 200), url: text(link.url).slice(0, 2e3) }));
40022
- return { title: text(context.title).slice(0, 200), paragraphs, links };
40161
+ const { html, ...context } = normalizeSessionContext(config?.sessionContext);
40162
+ if (!html) return context;
40163
+ const words = htmlToPlainText(html);
40164
+ return { ...context, paragraphs: words ? [words] : [] };
40023
40165
  };
40024
40166
  var idList = (value) => new Set((Array.isArray(value) ? value : []).map((id) => text(id).toLowerCase()).filter(Boolean));
40025
40167
  var stringOptions = (payload) => Array.isArray(payload.options) ? payload.options.filter((option) => typeof option === "string") : [];
@@ -40028,9 +40170,9 @@ var finiteNumber = (value) => {
40028
40170
  if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) return Number(value);
40029
40171
  return void 0;
40030
40172
  };
40031
- var SCALE_KEYS = ["min", "minimum", "max", "maximum", "minLabel", "lowLabel", "maxLabel", "highLabel"];
40173
+ var SCALE_KEYS = ["step", "min", "minimum", "max", "maximum", "minLabel", "lowLabel", "maxLabel", "highLabel"];
40032
40174
  var cleanScale = (record) => {
40033
- if (!isRecord6(record)) return {};
40175
+ if (!isRecord7(record)) return {};
40034
40176
  const out = {};
40035
40177
  SCALE_KEYS.forEach((key) => {
40036
40178
  if (typeof record[key] === "string" || typeof record[key] === "number" && Number.isFinite(record[key])) out[key] = record[key];
@@ -40099,7 +40241,9 @@ var sameAuthoringInputs = (stored, record) => {
40099
40241
  var progressNotes = (questions, mine) => ({
40100
40242
  ...questions.locked ? { lockedQuestions: questions.locked } : {},
40101
40243
  ...questions.complete === false ? { questionsComplete: false } : {},
40102
- ...mine.complete === false ? { myAnswersComplete: false, note: "This session is large: answers made outside this agent may not all be listed." } : {}
40244
+ ...questions.unread ? { unreadQuestions: questions.unread } : {},
40245
+ ...mine.unread ? { unreadMyAnswers: mine.unread } : {},
40246
+ ...mine.complete === false ? { myAnswersComplete: false, note: "Some answers could not be read or the scan is unfinished; the answers shown may be incomplete." } : {}
40103
40247
  });
40104
40248
  var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = () => Date.now(), sbt = null, createProvider, sleep, registry = {} }) => {
40105
40249
  const requestTestEth = createTestEthRequester({ config, wallet, createProvider, now, sleep });
@@ -40125,9 +40269,9 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40125
40269
  state = await updateAgentState(config.stateDir, mutate);
40126
40270
  };
40127
40271
  const profile = () => ({
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)
40272
+ displayName: trim8(state?.profile?.displayName ?? config.profile.displayName),
40273
+ modelId: trim8(state?.profile?.modelId ?? config.profile.modelId),
40274
+ platform: trim8(state?.profile?.platform ?? config.profile.platform)
40131
40275
  });
40132
40276
  const trustedOriginsFor = (preferred) => config.trustedAppOrigins.includes(preferred) ? [preferred, ...config.trustedAppOrigins.filter((origin) => origin !== preferred)] : config.trustedAppOrigins;
40133
40277
  const openSession = async (info) => {
@@ -40155,7 +40299,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40155
40299
  };
40156
40300
  const remember = (ctx, title, recruitmentSource) => saveState((fresh) => {
40157
40301
  const key = sessionKey(ctx.info);
40158
- const stored = isRecord6(fresh.sessions[key]) ? fresh.sessions[key] : {};
40302
+ const stored = isRecord7(fresh.sessions[key]) ? fresh.sessions[key] : {};
40159
40303
  const previous = stored.sessionId && stored.sessionId !== ctx.client.sessionId ? {} : stored;
40160
40304
  fresh.sessions[key] = {
40161
40305
  ...previous,
@@ -40172,12 +40316,12 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40172
40316
  });
40173
40317
  const sessionState = (ctx) => {
40174
40318
  const entry = state?.sessions?.[sessionKey(ctx.info)];
40175
- return isRecord6(entry) ? entry : {};
40319
+ return isRecord7(entry) ? entry : {};
40176
40320
  };
40177
40321
  const rememberAnswers = (ctx, answered) => saveState((fresh) => {
40178
40322
  const entry = fresh.sessions[sessionKey(ctx.info)];
40179
- if (!isRecord6(entry) || entry.sessionId !== ctx.client.sessionId) return;
40180
- entry.answers = isRecord6(entry.answers) ? entry.answers : {};
40323
+ if (!isRecord7(entry) || entry.sessionId !== ctx.client.sessionId) return;
40324
+ entry.answers = isRecord7(entry.answers) ? entry.answers : {};
40181
40325
  answered.forEach(({ questionId, record, answeredAt, chainOrder, requestId }) => {
40182
40326
  entry.answers[questionId] = {
40183
40327
  value: record.answer.value,
@@ -40190,7 +40334,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40190
40334
  });
40191
40335
  const resolveSession = async (session) => {
40192
40336
  await loadState();
40193
- const ref = trim7(session);
40337
+ const ref = trim8(session);
40194
40338
  if (/^https?:\/\//i.test(ref)) {
40195
40339
  const parsed = parseSessionLink(ref, { allowLocalWorkers: config.allowLocalWorkers });
40196
40340
  const key2 = sessionKey(parsed);
@@ -40206,7 +40350,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40206
40350
  if (!key) throw new Error(`No joined session named "${slug}". Join it first with join_session.`);
40207
40351
  }
40208
40352
  const entry = state.sessions[key];
40209
- if (!key || !isRecord6(entry)) throw new Error("No session joined yet. Call join_session with a session link.");
40353
+ if (!key || !isRecord7(entry)) throw new Error("No session joined yet. Call join_session with a session link.");
40210
40354
  return openSession({
40211
40355
  workerOrigin: normalizeWorkerOrigin(entry.workerOrigin, { allowLocalWorkers: config.allowLocalWorkers }),
40212
40356
  slug: normalizeSessionSlug(entry.slug),
@@ -40226,33 +40370,27 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40226
40370
  };
40227
40371
  const readCached = async (ctx, storageId) => {
40228
40372
  if (ctx.records.has(storageId)) return ctx.records.get(storageId);
40229
- if (now() - (ctx.missing.get(storageId) ?? -Infinity) < MISSING_RECORD_RETRY_MS) return null;
40230
- let payload = null;
40373
+ if (now() - (ctx.missing.get(storageId) ?? -Infinity) < MISSING_RECORD_RETRY_MS) return void 0;
40231
40374
  try {
40232
- payload = await ctx.client.read(storageId);
40375
+ const payload = await ctx.client.read(storageId);
40376
+ ctx.missing.delete(storageId);
40377
+ cacheRecord(ctx, storageId, payload);
40378
+ return payload;
40233
40379
  } catch (error) {
40234
40380
  if (!(error instanceof WorkerRequestError) || error.status !== 404) throw error;
40235
- }
40236
- if (payload === null) {
40237
40381
  ctx.missing.set(storageId, now());
40238
40382
  if (ctx.missing.size > MAX_CACHED_RECORDS) ctx.missing.delete(ctx.missing.keys().next().value);
40239
- } else {
40240
- ctx.missing.delete(storageId);
40241
- cacheRecord(ctx, storageId, payload);
40383
+ return void 0;
40242
40384
  }
40243
- return payload;
40244
40385
  };
40245
40386
  const readMany = async (ctx, storageIds) => {
40246
40387
  const deadline = now() + READ_DEADLINE_MS;
40247
- let complete = true;
40248
40388
  const payloads = await mapLimit(storageIds, READ_CONCURRENCY, async (storageId) => {
40249
- if (!ctx.records.has(storageId) && (now() >= deadline || signal?.aborted)) {
40250
- complete = false;
40251
- return void 0;
40252
- }
40253
- return readCached(ctx, storageId);
40389
+ if (!ctx.records.has(storageId) && (now() >= deadline || signal?.aborted)) return void 0;
40390
+ return readCached(ctx, storageId).catch(() => void 0);
40254
40391
  });
40255
- return { payloads, complete };
40392
+ const unread = payloads.filter((payload) => payload === void 0).length;
40393
+ return { payloads, complete: unread === 0, unread };
40256
40394
  };
40257
40395
  const loadQuestions = async (ctx) => {
40258
40396
  const sessionConfig = await ensureReady(ctx);
@@ -40262,7 +40400,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40262
40400
  const all2 = [];
40263
40401
  for (const row of result.items) {
40264
40402
  try {
40265
- if (!isRecord6(row.payload) || typeof row.payload.type !== "string") continue;
40403
+ if (!isRecord7(row.payload) || typeof row.payload.type !== "string") continue;
40266
40404
  const payload = isLockedQuestion(row.payload) ? { ...row.payload, prompt: "[encrypted]", locked: true } : row.payload;
40267
40405
  if (typeof payload.prompt !== "string") continue;
40268
40406
  all2.push({
@@ -40283,15 +40421,15 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40283
40421
  }
40284
40422
  const { items, complete } = await ctx.client.listAll("questions", { maxItems: MAX_QUESTIONS2, signal });
40285
40423
  const hidden = new Set(items.filter((item) => item.hidden === true).map((item) => text(item.metadata?.resourceId).toLowerCase()));
40286
- const rows = items.filter((item) => item.hidden !== true && text(item.storageRef?.id));
40287
- const { payloads, complete: readComplete } = await readMany(ctx, rows.map((item) => text(item.storageRef.id)));
40288
40424
  const blocked = idList(sessionConfig.BLOCKED_QUESTION_IDS);
40425
+ const rows = items.filter((item) => item.hidden !== true && text(item.storageRef?.id) && !hidden.has(text(item.metadata?.resourceId).toLowerCase()) && !blocked.has(text(item.metadata?.resourceId).toLowerCase()));
40426
+ const { payloads, complete: readComplete, unread } = await readMany(ctx, rows.map((item) => text(item.storageRef.id)));
40289
40427
  const highlighted = idList(sessionConfig.HIGHLIGHTED_QUESTION_IDS);
40290
40428
  const versions = /* @__PURE__ */ new Map();
40291
40429
  rows.forEach((item, index) => {
40292
40430
  try {
40293
40431
  const payload = payloads[index];
40294
- if (!isRecord6(payload) || typeof payload.prompt !== "string" || typeof payload.type !== "string") return;
40432
+ if (!isRecord7(payload) || typeof payload.prompt !== "string" || typeof payload.type !== "string") return;
40295
40433
  if (text(payload.sessionSlug) !== ctx.info.slug) return;
40296
40434
  if (text(payload.sessionId) && normalizeSessionIdHex(text(payload.sessionId)) !== ctx.client.sessionId) return;
40297
40435
  const id = (text(payload.id) || text(item.metadata?.resourceId)).toLowerCase();
@@ -40328,11 +40466,12 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40328
40466
  questions.locked = all.length - questions.length;
40329
40467
  questions.lockedIds = new Set(all.filter((question) => isLockedQuestion(question)).map((question) => question.id));
40330
40468
  questions.complete = complete && readComplete;
40469
+ questions.unread = unread;
40331
40470
  return questions;
40332
40471
  };
40333
40472
  const toEntries = async (ctx, items) => {
40334
- const rows = items.filter((item) => text(item.storageRef?.id));
40335
- const { payloads, complete } = await readMany(ctx, rows.map((item) => text(item.storageRef.id)));
40473
+ const rows = items.filter((item) => item.hidden !== true && text(item.storageRef?.id));
40474
+ const { payloads, complete, unread } = await readMany(ctx, rows.map((item) => text(item.storageRef.id)));
40336
40475
  const entries = rows.map((item, index) => ({
40337
40476
  storageId: text(item.storageRef.id),
40338
40477
  createdAt: text(item.metadata?.createdAt) || text(item.storageRef?.createdAt),
@@ -40340,22 +40479,23 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40340
40479
  payload: payloads[index]
40341
40480
  }));
40342
40481
  entries.complete = complete;
40482
+ entries.unread = unread;
40343
40483
  return entries;
40344
40484
  };
40345
40485
  const scanMyAnswers = async (ctx) => {
40346
- if (ctx.ownScan && (!ctx.registry || ctx.ownScan.complete) && now() - ctx.ownScan.at < OWN_ANSWER_SCAN_MAX_AGE_MS && ctx.ownScan.sessionId === ctx.client.sessionId) {
40486
+ if (ctx.ownScan && ctx.ownScan.complete && now() - ctx.ownScan.at < OWN_ANSWER_SCAN_MAX_AGE_MS && ctx.ownScan.sessionId === ctx.client.sessionId && ctx.ownScan.windowKey === ctx.registry?.windowKey) {
40347
40487
  return ctx.ownScan;
40348
40488
  }
40349
40489
  if (ctx.registry) {
40350
40490
  const questions = await loadQuestions(ctx);
40351
40491
  const questionIds = new Set(questions.map((question) => question.id));
40352
- const { entries: entries2, complete: complete2, chainOrders } = await ctx.registry.readAnswers({ questionIds, maxRecords: MAX_OWN_ANSWER_ITEMS, mine: true, signal });
40492
+ const { entries: entries2, complete: complete2, chainOrders, unreadAnswers } = await ctx.registry.readAnswers({ questionIds, maxRecords: MAX_OWN_ANSWER_ITEMS, mine: true, signal });
40353
40493
  const latest2 = latestAnswersByQuestion(entries2, { registryConfig: ctx.client.config, questionIds });
40354
40494
  const answers2 = /* @__PURE__ */ new Map();
40355
40495
  latest2.forEach((byResponder, id) => {
40356
40496
  if (byResponder.has(wallet.address.toLowerCase())) answers2.set(id, byResponder.get(wallet.address.toLowerCase()));
40357
40497
  });
40358
- ctx.ownScan = { at: now(), sessionId: ctx.client.sessionId, answers: answers2, complete: complete2, chainOrders };
40498
+ ctx.ownScan = { at: now(), sessionId: ctx.client.sessionId, windowKey: ctx.registry.windowKey, answers: answers2, complete: complete2, chainOrders, unread: unreadAnswers };
40359
40499
  return ctx.ownScan;
40360
40500
  }
40361
40501
  const { items, complete: listed } = await ctx.client.listAll("responses", {
@@ -40374,7 +40514,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40374
40514
  const entry = byResponder.get(self2);
40375
40515
  if (entry) answers.set(questionId, entry);
40376
40516
  });
40377
- ctx.ownScan = { at: now(), sessionId: ctx.client.sessionId, answers, complete };
40517
+ ctx.ownScan = { at: now(), sessionId: ctx.client.sessionId, answers, complete, unread: entries.unread };
40378
40518
  return ctx.ownScan;
40379
40519
  };
40380
40520
  const loadMyAnswers = async (ctx) => {
@@ -40384,10 +40524,10 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40384
40524
  let complete = scan.complete;
40385
40525
  const blocks = /* @__PURE__ */ new Map();
40386
40526
  await loadState();
40387
- const local = sessionState(ctx).sessionId === ctx.client.sessionId && isRecord6(sessionState(ctx).answers) ? sessionState(ctx).answers : {};
40527
+ const local = sessionState(ctx).sessionId === ctx.client.sessionId && isRecord7(sessionState(ctx).answers) ? sessionState(ctx).answers : {};
40388
40528
  for (const [questionId, answer] of Object.entries(local)) {
40389
- if (!isRecord6(answer)) continue;
40390
- if (ctx.registry && !scan.complete) continue;
40529
+ if (!isRecord7(answer)) continue;
40530
+ if (ctx.registry && (!scan.complete || !ctx.registry.containsBlock(answer.blockNumber))) continue;
40391
40531
  const known = mine.get(questionId);
40392
40532
  const chainOrder = scan.chainOrders?.get(questionId);
40393
40533
  if (ctx.registry && chainOrder && compareChainOrder(answer, chainOrder) < 0) continue;
@@ -40408,6 +40548,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40408
40548
  if (value === "" || value === null || value === void 0 || Array.isArray(value) && !value.length) mine.delete(questionId);
40409
40549
  });
40410
40550
  mine.complete = complete;
40551
+ mine.unread = scan.unread;
40411
40552
  return mine;
40412
40553
  };
40413
40554
  const sessionSummary = (ctx, sessionConfig) => ({
@@ -40423,7 +40564,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40423
40564
  address: wallet.address,
40424
40565
  profile: profile(),
40425
40566
  currentSession: state.sessions[state.current]?.slug || null,
40426
- sessions: await mapLimit(Object.values(state.sessions).filter(isRecord6), READ_CONCURRENCY, async (entry) => ({
40567
+ sessions: await mapLimit(Object.values(state.sessions).filter(isRecord7), READ_CONCURRENCY, async (entry) => ({
40427
40568
  session: entry.slug,
40428
40569
  title: entry.title || entry.slug,
40429
40570
  webUrl: buildSessionWebUrl(entry),
@@ -40444,9 +40585,9 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40444
40585
  },
40445
40586
  async joinSession({ link, worker, session, inviteCode, groupPassword, claimPassword } = {}) {
40446
40587
  let parsed;
40447
- if (trim7(link)) {
40588
+ if (trim8(link)) {
40448
40589
  parsed = parseSessionLink(link, { allowLocalWorkers: config.allowLocalWorkers });
40449
- } else if (trim7(worker) && trim7(session)) {
40590
+ } else if (trim8(worker) && trim8(session)) {
40450
40591
  parsed = {
40451
40592
  appOrigin: config.appOrigin,
40452
40593
  slug: normalizeSessionSlug(session),
@@ -40467,9 +40608,9 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40467
40608
  const offered = parsed.sbtClaims.filter((claim) => !claim.auto).map((claim) => claim.sbt);
40468
40609
  if (offered.length) outcome.sbtsToClaimWithClaimSbt = offered;
40469
40610
  const separateCodes = {
40470
- inviteCode: trim7(inviteCode),
40471
- groupPassword: trim7(groupPassword),
40472
- claimPassword: trim7(claimPassword)
40611
+ inviteCode: trim8(inviteCode),
40612
+ groupPassword: trim8(groupPassword),
40613
+ claimPassword: trim8(claimPassword)
40473
40614
  };
40474
40615
  const hasSeparateCodes = Object.values(separateCodes).some(Boolean);
40475
40616
  if (hasSeparateCodes && claims.length !== 1) {
@@ -40513,19 +40654,19 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40513
40654
  outcome.readOnlyReason = describeError(loginError);
40514
40655
  outcome.note = "Joined read-only: public questions and results are available, but uploads, answers, statements, and faucet requests need Worker access.";
40515
40656
  }
40516
- const scopes = isRecord6(tokenClaims?.scopes) ? tokenClaims.scopes : {};
40657
+ const scopes = isRecord7(tokenClaims?.scopes) ? tokenClaims.scopes : {};
40517
40658
  outcome.scopes = Object.keys(scopes).filter((scope) => scopes[scope] === true);
40518
40659
  if (parsed.joinGroupId) {
40519
40660
  checkpoint();
40520
40661
  try {
40521
40662
  if (ctx.registry) throw new Error("Worker groups are not available in on-chain sessions.");
40522
- await ctx.client.joinGroup(parsed.joinGroupId);
40663
+ await ctx.client.joinGroup(parsed.joinGroupId, { signal });
40523
40664
  outcome.joinedGroup = parsed.joinGroupId;
40524
40665
  } catch (error) {
40525
40666
  outcome.groupJoinError = describeError(error);
40526
40667
  }
40527
40668
  }
40528
- outcome.description = text(sessionConfig.sessionInfo).slice(0, MAX_CONTEXT_LENGTH);
40669
+ outcome.description = text(sessionConfig.sessionInfo).slice(0, MAX_DESCRIPTION_LENGTH);
40529
40670
  outcome.context = contextOf(sessionConfig);
40530
40671
  const sessionSbts = listSessionSbts(sessionConfig);
40531
40672
  if (sessionSbts.length) outcome.sessionSbts = sessionSbts;
@@ -40545,7 +40686,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40545
40686
  return {
40546
40687
  ...sessionSummary(ctx, sessionConfig),
40547
40688
  testEth: await ctx.client.describeTestEth(),
40548
- description: text(sessionConfig.sessionInfo).slice(0, MAX_CONTEXT_LENGTH),
40689
+ description: text(sessionConfig.sessionInfo).slice(0, MAX_DESCRIPTION_LENGTH),
40549
40690
  context: contextOf(sessionConfig),
40550
40691
  ...endsAt ? { endsAt } : {},
40551
40692
  ...sessionSbts.length ? { sessionSbts } : {},
@@ -40588,7 +40729,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40588
40729
  let reconciliationRequired = false;
40589
40730
  const seen = /* @__PURE__ */ new Set();
40590
40731
  for (const item of answers) {
40591
- const questionId = trim7(item?.questionId).toLowerCase();
40732
+ const questionId = trim8(item?.questionId).toLowerCase();
40592
40733
  if (seen.has(questionId)) {
40593
40734
  results.push({ questionId, ok: false, error: "This question appears more than once in the call; send one answer per question." });
40594
40735
  continue;
@@ -40633,13 +40774,14 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40633
40774
  let requestId;
40634
40775
  await saveState((fresh) => {
40635
40776
  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 : {};
40777
+ if (!isRecord7(entry) || entry.sessionId !== ctx.client.sessionId) throw new Error("Session identity changed; join again before answering.");
40778
+ entry.pendingUploads = isRecord7(entry.pendingUploads) ? entry.pendingUploads : {};
40638
40779
  const previous = entry.pendingUploads[questionId];
40639
- requestId = previous?.payloadDigest === digest && trim7(previous.requestId) ? previous.requestId : `answer:v1:${operationId}:${questionId}`;
40780
+ requestId = previous?.payloadDigest === digest && trim8(previous.requestId) ? previous.requestId : `answer:v1:${operationId}:${questionId}`;
40640
40781
  entry.pendingUploads[questionId] = { payloadDigest: digest, requestId };
40641
40782
  });
40642
- const storageRef = await ctx.client.upload({ resource: "responses", data, requestId });
40783
+ checkpoint();
40784
+ const storageRef = await ctx.client.upload({ resource: "responses", data, requestId, signal });
40643
40785
  if (text(storageRef?.id)) cacheRecord(ctx, text(storageRef.id), built.record);
40644
40786
  answered.push({ questionId, requestId, record: built.record, answeredAt: text(storageRef?.createdAt) || new Date(now()).toISOString() });
40645
40787
  results.push({ questionId, ok: true, answer: built.record.answer.value });
@@ -40726,7 +40868,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40726
40868
  ];
40727
40869
  checkpoint();
40728
40870
  try {
40729
- const storageRef = await ctx.client.upload({ resource: "questions", data: built.record, tags: tagList });
40871
+ const storageRef = await ctx.client.upload({ resource: "questions", data: built.record, tags: tagList, signal });
40730
40872
  if (text(storageRef?.id)) cacheRecord(ctx, text(storageRef.id), built.record);
40731
40873
  return { session: ctx.info.slug, questionId: built.id, created: true, prompt: built.record.prompt };
40732
40874
  } catch (error) {
@@ -40770,18 +40912,21 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40770
40912
  let entries;
40771
40913
  let complete;
40772
40914
  let listedCount;
40915
+ let unreadAnswers;
40773
40916
  const knownIds = new Set(questions.map((question) => question.id));
40774
40917
  if (ctx.registry) {
40775
40918
  const result = await ctx.registry.readAnswers({ questionIds: knownIds, maxRecords: cap, signal });
40776
40919
  entries = result.entries;
40777
40920
  complete = result.complete;
40778
40921
  listedCount = entries.length;
40922
+ unreadAnswers = result.unreadAnswers;
40779
40923
  } else {
40780
40924
  const result = await ctx.client.listAll("responses", { maxItems: MAX_RESULT_RECORDS, signal });
40781
40925
  const listed = result.items.filter((item) => text(item.storageRef?.id)).sort((left, right) => text(left.metadata?.createdAt) < text(right.metadata?.createdAt) ? 1 : -1);
40782
40926
  entries = await toEntries(ctx, listed.slice(0, cap));
40783
40927
  complete = result.complete;
40784
40928
  listedCount = listed.length;
40929
+ unreadAnswers = entries.unread;
40785
40930
  }
40786
40931
  const latest = latestAnswersByQuestion(entries, {
40787
40932
  sessionSlug: ctx.info.slug,
@@ -40794,6 +40939,7 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40794
40939
  resultsPublic: true,
40795
40940
  webUrl: buildSessionWebUrl(ctx.info),
40796
40941
  participants: participants.size,
40942
+ unreadAnswers,
40797
40943
  partial: !complete || listedCount > cap || questions.complete === false || entries.complete === false,
40798
40944
  questions: questions.filter((question) => !wanted || wanted.has(question.id)).map((question) => summarizeQuestion(question, latest.get(question.id)))
40799
40945
  };
@@ -40802,10 +40948,10 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40802
40948
  const ctx = await resolveSession(session);
40803
40949
  if (ctx.registry) throw new Error("Worker groups are not available in on-chain sessions.");
40804
40950
  await ensureReady(ctx);
40805
- const id = trim7(groupId);
40951
+ const id = trim8(groupId);
40806
40952
  if (!/^[A-Za-z0-9._:-]{1,128}$/.test(id)) throw new Error("Pass a valid group id.");
40807
40953
  checkpoint();
40808
- await ctx.client.joinGroup(id);
40954
+ await ctx.client.joinGroup(id, { signal });
40809
40955
  return { session: ctx.info.slug, joinedGroup: id };
40810
40956
  },
40811
40957
  async claimSbt({ session, sbt: sbtAddress, inviteCode, groupPassword, claimPassword } = {}) {
@@ -40817,10 +40963,10 @@ var createParticipant = ({ config, wallet, fetchImpl = globalThis.fetch, now = (
40817
40963
  });
40818
40964
  const result = await sbt.claim({
40819
40965
  ctx,
40820
- sbtAddress: trim7(sbtAddress),
40821
- inviteCode: trim7(inviteCode),
40822
- groupPassword: trim7(groupPassword),
40823
- claimPassword: trim7(claimPassword),
40966
+ sbtAddress: trim8(sbtAddress),
40967
+ inviteCode: trim8(inviteCode),
40968
+ groupPassword: trim8(groupPassword),
40969
+ claimPassword: trim8(claimPassword),
40824
40970
  signal
40825
40971
  });
40826
40972
  await ctx.client.login().catch(() => {
@@ -40844,7 +40990,7 @@ var sessionParam = {
40844
40990
  type: "string",
40845
40991
  description: "Session slug or link. Defaults to the session joined most recently."
40846
40992
  };
40847
- var text3 = (description, extra = {}) => ({ type: "string", description, ...extra });
40993
+ var text4 = (description, extra = {}) => ({ type: "string", description, ...extra });
40848
40994
  var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
40849
40995
  var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
40850
40996
  var SPENDS = { ...WRITE, destructiveHint: true };
@@ -40864,9 +41010,9 @@ var buildTools = (participant) => [
40864
41010
  inputSchema: {
40865
41011
  type: "object",
40866
41012
  properties: {
40867
- displayName: text3("Name shown in reports; up to 80 characters. Empty string clears it."),
40868
- modelId: text3("Model identifier, for example the model you run on."),
40869
- platform: text3("Agent platform or harness.")
41013
+ displayName: text4("Name shown in reports; up to 80 characters. Empty string clears it."),
41014
+ modelId: text4("Model identifier, for example the model you run on."),
41015
+ platform: text4("Agent platform or harness.")
40870
41016
  },
40871
41017
  additionalProperties: false
40872
41018
  },
@@ -40880,12 +41026,12 @@ var buildTools = (participant) => [
40880
41026
  inputSchema: {
40881
41027
  type: "object",
40882
41028
  properties: {
40883
- link: text3("Session link, including any invite parameters."),
40884
- worker: text3("Worker origin, when you have no link."),
40885
- session: text3("Session slug, when you have no link."),
40886
- inviteCode: text3("SBT invite code, if one was given separately from the link."),
40887
- groupPassword: text3("SBT group password, if one was given separately."),
40888
- claimPassword: text3("SBT claim password, if one was given separately.")
41029
+ link: text4("Session link, including any invite parameters."),
41030
+ worker: text4("Worker origin, when you have no link."),
41031
+ session: text4("Session slug, when you have no link."),
41032
+ inviteCode: text4("SBT invite code, if one was given separately from the link."),
41033
+ groupPassword: text4("SBT group password, if one was given separately."),
41034
+ claimPassword: text4("SBT claim password, if one was given separately.")
40889
41035
  },
40890
41036
  additionalProperties: false
40891
41037
  },
@@ -40911,7 +41057,7 @@ var buildTools = (participant) => [
40911
41057
  {
40912
41058
  name: "list_questions",
40913
41059
  title: "List questions",
40914
- description: "List the session\u2019s statements and questions with their type, options or scale, and your latest answer. Unanswered ones come first by default.",
41060
+ description: "List the session\u2019s statements and questions with their type, options or scale, and your latest answer. Unanswered ones come first by default. questionsComplete or myAnswersComplete is false when listing or payload reads are unfinished; unreadQuestions and unreadMyAnswers count listed payloads that could not be read.",
40915
41061
  inputSchema: {
40916
41062
  type: "object",
40917
41063
  properties: {
@@ -40927,7 +41073,7 @@ var buildTools = (participant) => [
40927
41073
  {
40928
41074
  name: "answer",
40929
41075
  title: "Answer questions",
40930
- description: 'Submit answers. Values by type: binary "Agree" | "Unsure" | "Disagree"; rating a whole number on the question\u2019s scale; multichoice the option text (an array when several are allowed); quadratic one signed whole-number vote per option whose squares sum within the credit budget; freeform text. Answering again replaces your previous answer.',
41076
+ description: 'Submit answers. Values by type: binary "Agree" | "Unsure" | "Disagree"; rating a number on the question\u2019s scale and step grid (default step 1); multichoice the option text (an array when several are allowed); quadratic one signed whole-number vote per option whose squares sum within the credit budget; freeform text. Answering again replaces your previous answer.',
40931
41077
  inputSchema: {
40932
41078
  type: "object",
40933
41079
  properties: {
@@ -40939,9 +41085,9 @@ var buildTools = (participant) => [
40939
41085
  items: {
40940
41086
  type: "object",
40941
41087
  properties: {
40942
- questionId: text3("Question id from list_questions."),
41088
+ questionId: text4("Question id from list_questions."),
40943
41089
  value: { description: "The answer value; see the tool description for each type." },
40944
- comment: text3("Optional reasoning shown with your answer (up to 4000 characters)."),
41090
+ comment: text4("Optional reasoning shown with your answer (up to 4000 characters)."),
40945
41091
  importance: { type: "integer", minimum: 0, maximum: 10, description: "How much this question matters to you." },
40946
41092
  conviction: { type: "integer", minimum: 0, maximum: 10, description: "How sure you are of your answer." }
40947
41093
  },
@@ -40964,7 +41110,7 @@ var buildTools = (participant) => [
40964
41110
  type: "object",
40965
41111
  properties: {
40966
41112
  session: sessionParam,
40967
- prompt: text3("The statement or question text."),
41113
+ prompt: text4("The statement or question text."),
40968
41114
  type: { type: "string", enum: ["binary", "rating", "multichoice", "quadratic", "freeform"] },
40969
41115
  options: { type: "array", items: { type: "string" }, description: "Options for multichoice or quadratic." },
40970
41116
  singleSelect: { type: "boolean", description: "Multichoice: allow only one option." },
@@ -40980,7 +41126,7 @@ var buildTools = (participant) => [
40980
41126
  {
40981
41127
  name: "get_results",
40982
41128
  title: "Get results",
40983
- description: "Summarize everyone\u2019s latest answers per question when the session publishes results: agree/unsure/disagree counts, rating means, option counts, and how many answers came from self-declared agents.",
41129
+ description: "Summarize everyone\u2019s latest answers per question when the session publishes results: agree/unsure/disagree counts, rating means, option counts, and how many answers came from self-declared agents. partial is true when a scan is unfinished, capped, or any listed question or answer payload could not be read. unreadAnswers counts the selected listed answer records that could not be read; readable older answers may still appear until those records recover.",
40984
41130
  inputSchema: {
40985
41131
  type: "object",
40986
41132
  properties: {
@@ -40999,7 +41145,7 @@ var buildTools = (participant) => [
40999
41145
  description: "Join an open group in the session by its id (from a ?joinGroup= link).",
41000
41146
  inputSchema: {
41001
41147
  type: "object",
41002
- properties: { session: sessionParam, groupId: text3("Group id.") },
41148
+ properties: { session: sessionParam, groupId: text4("Group id.") },
41003
41149
  required: ["groupId"],
41004
41150
  additionalProperties: false
41005
41151
  },
@@ -41014,10 +41160,10 @@ var buildTools = (participant) => [
41014
41160
  type: "object",
41015
41161
  properties: {
41016
41162
  session: sessionParam,
41017
- sbt: text3("SBT contract address."),
41018
- inviteCode: text3("Invite code, when the SBT uses invites."),
41019
- groupPassword: text3("Group password, when the SBT uses one."),
41020
- claimPassword: text3("Claim password, when the SBT uses per-claim passwords.")
41163
+ sbt: text4("SBT contract address."),
41164
+ inviteCode: text4("Invite code, when the SBT uses invites."),
41165
+ groupPassword: text4("Group password, when the SBT uses one."),
41166
+ claimPassword: text4("Claim password, when the SBT uses per-claim passwords.")
41021
41167
  },
41022
41168
  required: ["sbt"],
41023
41169
  additionalProperties: false