@extrovert.dev/sdk 0.1.0-pre.7 → 0.1.0-pre.8

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
@@ -30,7 +30,7 @@ for a stable release:
30
30
  npm install @extrovert.dev/sdk@next
31
31
  ```
32
32
 
33
- Pin `@extrovert.dev/sdk@0.1.0-pre.7` when a dogfood test needs a reproducible contract snapshot.
33
+ Pin `@extrovert.dev/sdk@0.1.0-pre.8` when a dogfood test needs a reproducible contract snapshot.
34
34
  Requires Node 18+ for global `fetch` and Web Crypto.
35
35
 
36
36
  ## Build and use from source
@@ -529,7 +529,7 @@ a wire protocol (there is no `/v1/contract` endpoint).
529
529
  ```ts
530
530
  import { CONTRACT_VERSION, CONTRACT_MANIFEST } from "@extrovert.dev/sdk";
531
531
 
532
- CONTRACT_VERSION; // "0.1.0-pre.7": provisional, pre-1.0; pin it
532
+ CONTRACT_VERSION; // "0.1.0-pre.8": provisional, pre-1.0; pin it
533
533
  CONTRACT_MANIFEST.stability; // "provisional"
534
534
  CONTRACT_MANIFEST.core_shapes; // ["ReviewIntent","ReviewFeedback","DiffJson","Rule","ReviewEvent"]
535
535
  ```
@@ -550,7 +550,7 @@ page and the agent skills (`extrovert-send-email`, `extrovert-writing-rules`).
550
550
  The Review-Loop shapes are an **open, documented contract: versioned *with* this SDK** (not a wire
551
551
  protocol; there is no `/v1/contract` endpoint). Three guarantees:
552
552
 
553
- - **One version, everywhere.** `CONTRACT_VERSION` is **`0.1.0-pre.7`**, reconciled across the SDK package
553
+ - **One version, everywhere.** `CONTRACT_VERSION` is **`0.1.0-pre.8`**, reconciled across the SDK package
554
554
  version, the MCP server, and the OpenAPI `info.version`. Pin it; pin `CONTRACT_MANIFEST` for the
555
555
  exact shape set you built against.
556
556
  - **Named, documented types.** The five canonical shapes: `ReviewIntent`, `ReviewFeedback`,
@@ -581,7 +581,7 @@ EXTROVERT_API_BASE_URL=mock npx tsx examples/wait-for-otp.ts
581
581
 
582
582
  ## Status
583
583
 
584
- > **Note.** This source SDK tracks the `/v1` contract at `CONTRACT_VERSION` `0.1.0-pre.7`: a
584
+ > **Note.** This source SDK tracks the `/v1` contract at `CONTRACT_VERSION` `0.1.0-pre.8`: a
585
585
  > deliberate **prerelease**, pre-1.0, expect additive change. The offline `mock` transport models the
586
586
  > live server closely enough to reproduce a 422 `intent_required` and a queued review, so build and
587
587
  > test against it before you have a key. Install from the `next` tag until a stable release is cut.
package/dist/index.cjs CHANGED
@@ -268,7 +268,7 @@ var CURRENT_API_VERSION = "2026-06-23";
268
268
  var API_VERSION_HEADER = "Extrovert-Version";
269
269
 
270
270
  // src/http.ts
271
- var SDK_VERSION = "0.1.0-pre.7";
271
+ var SDK_VERSION = "0.1.0-pre.8";
272
272
  function buildUrl(baseUrl, path, query) {
273
273
  const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
274
274
  const rel = path.startsWith("/") ? path : `/${path}`;
@@ -938,6 +938,8 @@ ${parent.text}`;
938
938
  var MockBackend = class {
939
939
  constructor() {
940
940
  this.state = freshState();
941
+ /** Save / edit a rule (mock) - append-only by supersession (D11). */
942
+ this.learnedRules = /* @__PURE__ */ new Map();
941
943
  }
942
944
  reset() {
943
945
  this.state = freshState();
@@ -2042,7 +2044,27 @@ var MockBackend = class {
2042
2044
  composition_token_expires_at: params.scope ? void 0 : new Date(Date.now() + 6e5).toISOString()
2043
2045
  };
2044
2046
  }
2045
- /** Save / edit a rule (mock) - append-only by supersession (D11). */
2047
+ learnReviewRule(reviewId, req) {
2048
+ const fingerprint = JSON.stringify({ reviewId, req });
2049
+ const prior = this.learnedRules.get(req.client_id);
2050
+ if (prior) {
2051
+ if (prior.fingerprint !== fingerprint) throw new ValidationError({ status: 409, code: "conflict", message: "learning retry identity changed" });
2052
+ return structuredClone(prior.result);
2053
+ }
2054
+ const turn = (this.state.reviewTurns.get(reviewId) ?? []).find((t) => t.id === req.source_turn_id);
2055
+ if (!turn || turn.actor_kind !== "human" || !turn.actor_id) throw new ValidationError({ status: 403, code: "forbidden_scope", message: "learning requires authenticated human feedback" });
2056
+ const rule = this.saveRule({ ...req, scope: req.target === "category" ? "category" : "general", source_review_id: reviewId, source_turn_id: turn.id });
2057
+ rule.source_turn_id = turn.id;
2058
+ rule.rule_layer = req.target === "org_house" ? "org" : "project";
2059
+ if (req.target === "org_house") delete rule.project_id;
2060
+ rule.source_review_id = reviewId;
2061
+ rule.source_turn_id = turn.id;
2062
+ const audit = [...this.state.ruleAudit.values()].find((entry) => entry.entity_id === rule.id);
2063
+ audit.after_json = ruleSnapshotJSON(rule);
2064
+ const result = { rule, source_review_id: reviewId, source_turn_id: turn.id, human_id: turn.actor_id, audit_id: audit.id, propagation: "queued" };
2065
+ this.learnedRules.set(req.client_id, { fingerprint, result: structuredClone(result) });
2066
+ return result;
2067
+ }
2046
2068
  saveRule(req) {
2047
2069
  const text = req.rule_text.trim();
2048
2070
  if (!text) {
@@ -3683,6 +3705,7 @@ var HttpTransport = class {
3683
3705
  state: Array.isArray(params.state) ? params.state.join(",") : params.state,
3684
3706
  category_id: params.category_id,
3685
3707
  inbox: params.inbox,
3708
+ composer: params.composer,
3686
3709
  limit: params.limit,
3687
3710
  page: params.page
3688
3711
  };
@@ -3765,10 +3788,12 @@ var HttpTransport = class {
3765
3788
  });
3766
3789
  }
3767
3790
  waitForReviewEvent(params, signal) {
3791
+ const waitSeconds = Math.min(55, Math.max(1, params.wait_seconds ?? 55));
3768
3792
  return this.call({
3769
3793
  method: "GET",
3770
3794
  path: "/v1/reviews/events/wait",
3771
- query: { review_id: params.review_id, limit: params.limit, wait_seconds: params.wait_seconds },
3795
+ query: { review_id: params.review_id, limit: params.limit, wait_seconds: waitSeconds },
3796
+ timeoutMs: (waitSeconds + 10) * 1e3,
3772
3797
  signal
3773
3798
  });
3774
3799
  }
@@ -3837,6 +3862,9 @@ var HttpTransport = class {
3837
3862
  signal
3838
3863
  });
3839
3864
  }
3865
+ learnReviewRule(reviewId, req, signal) {
3866
+ return this.call({ method: "POST", path: `/v1/reviews/${encodeURIComponent(reviewId)}/learned-rules`, body: req, signal });
3867
+ }
3840
3868
  saveRule(req, signal) {
3841
3869
  return this.call({
3842
3870
  method: "PUT",
@@ -4244,6 +4272,9 @@ var MockTransport = class {
4244
4272
  async getRules(params) {
4245
4273
  return this.backend.getRules(params);
4246
4274
  }
4275
+ async learnReviewRule(reviewId, req) {
4276
+ return this.backend.learnReviewRule(reviewId, req);
4277
+ }
4247
4278
  async saveRule(req) {
4248
4279
  return this.backend.saveRule(req);
4249
4280
  }
@@ -5273,6 +5304,10 @@ var Rules = class {
5273
5304
  constructor(ctx) {
5274
5305
  this.ctx = ctx;
5275
5306
  }
5307
+ /** Learn category or organization house rules from verified human review feedback. */
5308
+ learnFromReview(reviewId, req, signal) {
5309
+ return this.ctx.transport.learnReviewRule(reviewId, req, signal);
5310
+ }
5276
5311
  /** Get the ORDERED active rule set (precedence ladder applied; NO LLM). */
5277
5312
  get(params = {}, signal) {
5278
5313
  return this.ctx.transport.getRules(params, signal);
@@ -5280,7 +5315,7 @@ var Rules = class {
5280
5315
  /**
5281
5316
  * Save / edit a rule (append-only by supersession; D11). An agent-plane save is
5282
5317
  * ALWAYS project-layer: the saved rule's `rule_layer` is `project`, bound to the
5283
- * key's project. Agents cannot author org-layer / house-style (`rule_layer:"org"`)
5318
+ * key's project. For org-layer house rules use learnFromReview with an authenticated human source; this method cannot author (`rule_layer:"org"`)
5284
5319
  * rules in v1: that is a console/admin action.
5285
5320
  */
5286
5321
  save(req, signal) {
@@ -5558,7 +5593,7 @@ async function signWebhook(secret, body, timestampSeconds) {
5558
5593
  }
5559
5594
 
5560
5595
  // src/contract.ts
5561
- var CONTRACT_VERSION = "0.1.0-pre.7";
5596
+ var CONTRACT_VERSION = "0.1.0-pre.8";
5562
5597
  var CONTRACT_MANIFEST = {
5563
5598
  name: "extrovert.review-loop",
5564
5599
  version: CONTRACT_VERSION,
@@ -5589,6 +5624,8 @@ var CONTRACT_MANIFEST = {
5589
5624
  // realtime (M3)
5590
5625
  "ReviewEventReason",
5591
5626
  "ReviewEventsResult",
5627
+ "LearnReviewRuleRequest",
5628
+ "LearnedReviewRule",
5592
5629
  "ReviewEventCursor",
5593
5630
  // chat / revision / restamp (M5/M7)
5594
5631
  "PostReviewChatRequest",