@extrovert.dev/sdk 0.1.0-pre.16 → 0.1.0-pre.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -590,6 +590,23 @@ EXTROVERT_API_BASE_URL=mock npx tsx examples/wait-for-otp.ts
590
590
 
591
591
  MIT © Message Science. *A side gate for agents.*
592
592
 
593
+ ## Incoming-email activation
594
+
595
+ When signup returns `activation_method: "incoming_email"`, ask the human to send an
596
+ email from `human_email` to `address`. The inbox reservation expires at
597
+ `activation_expires_at`. Keep the temporary key and call `activationStatus()` until
598
+ `state` is `"proven"`, then call `verify({})` to receive the durable key. Pending keys
599
+ cannot read or send mail. `correctActivationEmail(humanEmail, revision)` clears any
600
+ previous proof and keeps the original deadline. Legacy signup responses that issued
601
+ an OTP still require that OTP until its original expiry.
602
+
603
+ Mail responses expose advisory `X-Extrovert-Storage-*` headers from 90% usage.
604
+ Use the exported `storageWarningFromHeaders(response.headers)` in a custom fetch
605
+ wrapper to read the threshold, byte counts, and cleanup/billing links without
606
+ consuming the response body. A missing or unavailable measurement is not zero usage.
607
+ Native writes enforce the cap; reads and deletion remain available. Deduplicate
608
+ human-facing suggestions within your session instead of repeating them on every read.
609
+
593
610
  For choosing access and handing setup over to workers, read
594
611
  [Connections and access](https://docs.extrovert.dev/concepts/connections-and-access/).
595
612
 
package/dist/index.cjs CHANGED
@@ -1122,11 +1122,31 @@ ${parent.text}`;
1122
1122
  }
1123
1123
  var MockBackend = class {
1124
1124
  constructor() {
1125
+ this.incomingActivation = false;
1125
1126
  this.administrativeFixtures = new AdministrativeFixtures();
1126
1127
  this.state = freshState();
1127
1128
  /** Save / edit a rule (mock) - append-only by supersession (D11). */
1128
1129
  this.learnedRules = /* @__PURE__ */ new Map();
1129
1130
  }
1131
+ activationStatus() {
1132
+ const activation = this.pendingActivation;
1133
+ if (!activation) throw new Error("No incoming-email activation exists");
1134
+ if (activation.expires_ms <= Date.now() && activation.state !== "activated") activation.state = "expired";
1135
+ return { ...activation };
1136
+ }
1137
+ correctActivationEmail(email, revision) {
1138
+ const activation = this.activationStatus();
1139
+ if (activation.revision !== revision || !["pending", "proven"].includes(activation.state)) throw new Error("Activation changed or expired");
1140
+ if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) throw new Error("Invalid human email");
1141
+ this.pendingActivation = { ...activation, human_email: email, revision: revision + 1, state: "pending" };
1142
+ return this.activationStatus();
1143
+ }
1144
+ /** Test-only trusted delivery; never available through a production client. */
1145
+ proveFixtureActivation(sender) {
1146
+ const activation = this.activationStatus();
1147
+ if (activation.state !== "pending" || sender !== activation.human_email) throw new Error("Activation sender mismatch or expired");
1148
+ this.pendingActivation = { ...activation, state: "proven" };
1149
+ }
1130
1150
  configureAdministrativeFixture(credential) {
1131
1151
  this.administrativeFixtures = new AdministrativeFixtures(credential);
1132
1152
  }
@@ -1172,6 +1192,10 @@ var MockBackend = class {
1172
1192
  const agentId = existing?.agentId ?? rid("agt");
1173
1193
  const address = existing?.address ?? `${validatedSharedLocalPart(req.username ?? randomHandle())}@${FREE_SHARED_DOMAIN}`;
1174
1194
  const otp = "492013";
1195
+ if (this.incomingActivation) {
1196
+ if (existing) throw new Error("Activation already pending; use the existing key");
1197
+ this.pendingActivation = { agent_id: agentId, address, human_email: email, created_ms: Date.now(), expires_ms: Date.now() + 864e5, revision: 1, state: "pending" };
1198
+ }
1175
1199
  this.state.signupByEmail.set(email, { customerId, agentId, address, otp, verified: false });
1176
1200
  return {
1177
1201
  customer_id: customerId,
@@ -1181,16 +1205,24 @@ var MockBackend = class {
1181
1205
  scopes: ["signup:verify"],
1182
1206
  address,
1183
1207
  verified: false,
1184
- otp_sent_to: email,
1185
- otp_expires_at: new Date(Date.now() + 15 * 60 * 1e3).toISOString(),
1186
- message: "A verification code was sent to your email. Call verify with it."
1208
+ ...this.incomingActivation ? {
1209
+ activation_method: "incoming_email",
1210
+ human_email: email,
1211
+ activation_expires_at: new Date(this.pendingActivation.expires_ms).toISOString(),
1212
+ message: `Your agent\u2019s inbox is almost ready. Send an email from ${email} to ${address} to activate it and link it to your human email.`
1213
+ } : {
1214
+ otp_sent_to: email,
1215
+ otp_expires_at: new Date(Date.now() + 15 * 60 * 1e3).toISOString(),
1216
+ message: "A verification code was sent to your email. Call verify with it."
1217
+ }
1187
1218
  };
1188
1219
  }
1189
1220
  /** Confirm a signup OTP and return a full-scope key (mock). */
1190
1221
  verify(req) {
1191
1222
  for (const [, s] of this.state.signupByEmail) {
1192
- if (!s.verified && s.otp === req.otp.trim()) {
1223
+ if (!s.verified && (this.incomingActivation ? this.activationStatus().state === "proven" && this.pendingActivation?.agent_id === s.agentId : s.otp === (req.otp ?? "").trim())) {
1193
1224
  s.verified = true;
1225
+ if (this.incomingActivation && this.pendingActivation) this.pendingActivation.state = "activated";
1194
1226
  return {
1195
1227
  agent_id: s.agentId,
1196
1228
  agent_key: `pk_agent_${s.agentId.slice(4)}_${rid("sk").slice(3)}`,
@@ -3564,6 +3596,12 @@ var HttpTransport = class {
3564
3596
  signUp(req, signal) {
3565
3597
  return this.call({ method: "POST", path: "/v1/agent/sign-up", body: req, signal });
3566
3598
  }
3599
+ activationStatus(signal) {
3600
+ return this.call({ method: "GET", path: "/v1/agent/activation", signal });
3601
+ }
3602
+ correctActivationEmail(human_email, revision, signal) {
3603
+ return this.call({ method: "PATCH", path: "/v1/agent/activation", body: { human_email, revision }, signal });
3604
+ }
3567
3605
  verify(req, signal) {
3568
3606
  return this.call({ method: "POST", path: "/v1/agent/verify", body: req, signal });
3569
3607
  }
@@ -4196,6 +4234,12 @@ var MockTransport = class {
4196
4234
  async signUp(req) {
4197
4235
  return this.backend.signUp(req);
4198
4236
  }
4237
+ async activationStatus() {
4238
+ return this.backend.activationStatus();
4239
+ }
4240
+ async correctActivationEmail(email, revision) {
4241
+ return this.backend.correctActivationEmail(email, revision);
4242
+ }
4199
4243
  async verify(req) {
4200
4244
  return this.backend.verify(req);
4201
4245
  }
@@ -5739,6 +5783,12 @@ var ExtrovertClient = class _ExtrovertClient {
5739
5783
  * Pending verification is also fail-closed with 403 `signup_disabled` while
5740
5784
  * free signup is paused.
5741
5785
  */
5786
+ activationStatus(signal) {
5787
+ return this.transport.activationStatus(signal);
5788
+ }
5789
+ correctActivationEmail(human_email, revision, signal) {
5790
+ return this.transport.correctActivationEmail(human_email, revision, signal);
5791
+ }
5742
5792
  verify(req, signal) {
5743
5793
  return this.transport.verify(req, signal);
5744
5794
  }
@@ -5906,7 +5956,7 @@ async function signWebhook(secret, body, timestampSeconds) {
5906
5956
  }
5907
5957
 
5908
5958
  // src/contract.ts
5909
- var CONTRACT_VERSION = "0.1.0-pre.16";
5959
+ var CONTRACT_VERSION = "0.1.0-pre.17";
5910
5960
  var CONTRACT_MANIFEST = {
5911
5961
  name: "extrovert.review-loop",
5912
5962
  version: CONTRACT_VERSION,
@@ -5982,6 +6032,26 @@ var CONTRACT_MANIFEST = {
5982
6032
  skills: ["extrovert-send-email", "extrovert-writing-rules"]
5983
6033
  };
5984
6034
 
6035
+ // src/storage.ts
6036
+ function storageWarningFromHeaders(headers) {
6037
+ if (headers.get("x-extrovert-storage-status") !== "available") return;
6038
+ const threshold = Number(headers.get("x-extrovert-storage-threshold"));
6039
+ if (!headers.has("x-extrovert-storage-used-bytes") || !headers.has("x-extrovert-storage-limit-bytes")) return;
6040
+ const used = Number(headers.get("x-extrovert-storage-used-bytes"));
6041
+ const limit = Number(headers.get("x-extrovert-storage-limit-bytes"));
6042
+ if (![90, 95, 99, 100].includes(threshold) || !Number.isSafeInteger(used) || used < 0 || !Number.isSafeInteger(limit) || limit < 0) return;
6043
+ const link = (name) => {
6044
+ const raw = headers.get(name);
6045
+ if (!raw || raw.length > 2048) return;
6046
+ try {
6047
+ const url = new URL(raw);
6048
+ if (url.protocol === "https:" && !url.username && !url.password) return url.href;
6049
+ } catch {
6050
+ }
6051
+ };
6052
+ return { threshold, used_bytes: used, limit_bytes: limit, cleanup_url: link("x-extrovert-storage-cleanup-url"), billing_url: link("x-extrovert-storage-billing-url") };
6053
+ }
6054
+
5985
6055
  exports.ADMINISTRATIVE_FIXTURE_KEY = ADMINISTRATIVE_FIXTURE_KEY;
5986
6056
  exports.API_VERSION_HEADER = API_VERSION_HEADER;
5987
6057
  exports.Administration = Administration;
@@ -6049,6 +6119,7 @@ exports.reviewIdOf = reviewIdOf;
6049
6119
  exports.sentMessageIdOf = sentMessageIdOf;
6050
6120
  exports.serializeInclude = serializeInclude;
6051
6121
  exports.signWebhook = signWebhook;
6122
+ exports.storageWarningFromHeaders = storageWarningFromHeaders;
6052
6123
  exports.threadIdOf = threadIdOf;
6053
6124
  exports.tierAllowsOrgWildcard = tierAllowsOrgWildcard;
6054
6125
  exports.tierNeedsExplicitBreadth = tierNeedsExplicitBreadth;