@mcp-index/sdk 0.17.0 → 0.18.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ additive feature, patch = fix). Backward-compatible additions only in 0.2.0.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## 0.18.1 - 2026-10-13
9
+
10
+ ### Changed
11
+
12
+ - A `tools/list` read through `requestStream`, on the session or on `experimental.tasks`, goes through the same listing filter as `listTools`. Before, a tool that `listTools` leaves out still came back that way.
13
+ - With `scanResults`, a result that arrives through a stream is scanned like a `callTool` result: `requestStream` with `tools/call` on either seam, and `experimental.tasks.callToolStream`. So is a task's result read later through `request({method: "tasks/result"})`, `getTaskResult` or `experimental.tasks.getTaskResult`, under the name of the tool whose gated call created the task. Without `scanResults` nothing changes.
14
+
15
+ ## 0.18.0 - 2026-10-11
16
+
17
+ ### Security
18
+
19
+ - Fixes GHSA-phh5-4p9f-4qmg (https://github.com/mcpindex-ai/mcpindex-sdk-ts/security/advisories/GHSA-phh5-4p9f-4qmg), cases where changed or new tool text reached the agent under guard and strict. Affected: @mcp-index/sdk < 0.18.0 and mcpindex-gate < 0.20.0.
20
+
21
+ ### Changed
22
+
23
+ - The wrapper reads the server's `instructions` after connect, pins them beside the tool pins (`.<server>.context.json`, the same format the gate's proxy writes), and reports a first pin or a change through the new `onContextDrift` option and one stderr line. It never changes what `getInstructions()` returns. It needs a file pin store. A numbers-only change is reported; this SDK does not apply the gate's digit budget.
24
+ - Under guard and strict a pinned tool that adds an outputSchema is left out once for review and its call holds when the schema carries any string or any name under properties, patternProperties, $defs or definitions. A property name counts, so expect one review per tool when a server starts returning structured output. A bare `{"type": "object"}` is still shown.
25
+ - The first paginated list on an empty pin store pins every page. Before, page 2 was held as new tools on an already-pinned server. Only the list that opened on the empty store can do this, only while it is current, and only while every pin was written by it; a list_changed, any call, a newer list or the last page ends it.
26
+ - A tool whose title is not a string is left out of the list as a wrong type, under guard and strict, and the title is never recorded. The field-set marker moves to 3; any marker from 2 up, including one from a newer version, counts as complete.
27
+ - Pending records carry the field-set version. Approving one written by 0.16 leaves the pin to record its fields on the next list, so the tool is not held again right after approval; one written by 0.17 keeps its version.
28
+ - A tool whose `annotations` key is present with the value `undefined` (an in-process McpServer tool registered without annotations) is shown. Before, it was left out as a wrong type.
29
+
8
30
  ## 0.17.0 - 2026-10-11
9
31
 
10
32
  ### Changed
package/README.md CHANGED
@@ -75,8 +75,13 @@ The pin records every top-level field a server lists for a tool (`title`,
75
75
  `icons`, `_meta`, `execution`, and any later field) and annotation keys other
76
76
  than the four hints. A change to one of them on a pinned tool leaves the tool
77
77
  out under guard and strict. A pin written before 0.17.0 records those fields
78
- once, from the first matching list. First-time `outputSchema` text on an
79
- otherwise unchanged tool is still shown.
78
+ once, from the first matching list. A first `outputSchema` is held the same way when it
79
+ carries any string or property name, which covers most structured output: expect
80
+ one review per tool when a server starts returning it.
81
+
82
+ With a file pin store, the wrapper also reads the server's `instructions` after
83
+ connect, pins them, and reports a first pin or a change through
84
+ `onContextDrift`. `getInstructions()` still returns what the server sent.
80
85
 
81
86
  ## Sign in (optional) - `mcpindex login`
82
87
 
@@ -23,6 +23,10 @@
23
23
  *
24
24
  * HONEST VOICE. Never the words protected / secured / safe in any emitted line.
25
25
  */
26
+ /** Sanitize a remote-server-controlled label (tool/server name) before it is rendered into a
27
+ * stderr line: drop ANSI escapes / CR / LF / other control bytes and bound the length. Defends
28
+ * the terminal and any downstream log parser against forged-line / escape-sequence injection. */
29
+ export declare function safeLabel(value: unknown): string;
26
30
  /** Cadence. FIRST_TOUCH (default): one line per distinct tool, then silent + a session
27
31
  * summary. SUMMARY: no per-call lines, only the end-of-session footer. EVERY: a line on
28
32
  * every call (loud — opt-in only). OFF: nothing. */
@@ -1 +1 @@
1
- {"version":3,"file":"ambient.d.ts","sourceRoot":"","sources":["../../src/ambient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAwBH;;oDAEoD;AACpD,oBAAY,WAAW;IACrB,WAAW,gBAAgB;IAC3B,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,GAAG,QAAQ;CACZ;AAED,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AASlD;;;;wFAIwF;AACxF,wBAAgB,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,CAetD;AAED;6EAC6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;4CAE4C;AAC5C,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;AAE1D,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B;AAED;;yFAEyF;AACzF,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAqBzC,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CACnC;AAED;;wDAEwD;AACxD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,GAAE,sBAA2B;IAMhD,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,eAAe;IAQpE,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;gFAC4E;IAC5E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,kBAAkB,GAAG,WAAW;IAa/F;;6BAEyB;IACzB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,kBAAkB,GAAG,WAAW,GAAG,IAAI;IAmB3G;;2FAEuF;IACvF,cAAc,CAAC,MAAM,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,MAAM,GAAG,IAAI;IAahF;oFACgF;IAChF,OAAO,CAAC,IAAI;CAmBb"}
1
+ {"version":3,"file":"ambient.d.ts","sourceRoot":"","sources":["../../src/ambient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAgBH;;iGAEiG;AACjG,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGhD;AAED;;oDAEoD;AACpD,oBAAY,WAAW;IACrB,WAAW,gBAAgB;IAC3B,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,GAAG,QAAQ;CACZ;AAED,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AASlD;;;;wFAIwF;AACxF,wBAAgB,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,CAetD;AAED;6EAC6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;4CAE4C;AAC5C,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;AAE1D,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B;AAED;;yFAEyF;AACzF,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAqBzC,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CACnC;AAED;;wDAEwD;AACxD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,GAAE,sBAA2B;IAMhD,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,eAAe;IAQpE,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;gFAC4E;IAC5E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,kBAAkB,GAAG,WAAW;IAa/F;;6BAEyB;IACzB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,kBAAkB,GAAG,WAAW,GAAG,IAAI;IAmB3G;;2FAEuF;IACvF,cAAc,CAAC,MAAM,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,MAAM,GAAG,IAAI;IAahF;oFACgF;IAChF,OAAO,CAAC,IAAI;CAmBb"}
@@ -38,7 +38,7 @@ const CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g;
38
38
  /** Sanitize a remote-server-controlled label (tool/server name) before it is rendered into a
39
39
  * stderr line: drop ANSI escapes / CR / LF / other control bytes and bound the length. Defends
40
40
  * the terminal and any downstream log parser against forged-line / escape-sequence injection. */
41
- function safeLabel(value) {
41
+ export function safeLabel(value) {
42
42
  const text = typeof value === "string" ? value : String(value);
43
43
  return text.replace(CONTROL_CHARS, "").slice(0, MAX_LABEL);
44
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ambient.js","sourceRoot":"","sources":["../../src/ambient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ambient.js","sourceRoot":"","sources":["../../src/ambient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The server's `instructions`, observed and pinned (port of Python
3
+ * `context_observe` / `context_pin`, observation only).
4
+ *
5
+ * MCP hosts put a server's `instructions` into the agent's context on connect.
6
+ * The tool hash does not cover them, so a server can keep every tool identical
7
+ * and rewrite the text the model reads first. The wrapper reads them after
8
+ * connect, pins them on first sight beside the tool pins, and reports a change.
9
+ * It never changes what `getInstructions()` returns: stripping the text removes
10
+ * the server's own guidance, replaying a pinned copy freezes first-connect text,
11
+ * and holding calls is a denial of service whoever controls the text can aim.
12
+ *
13
+ * The file is the same format the Python store writes
14
+ * (`.<stem>.context.json`, schema_version 1, a map of server id to pin), so a
15
+ * proxy and a wrapper on one server share a baseline. Limits against Python: no
16
+ * digit-budget carve-out (a numbers-only change is reported as
17
+ * `numeric-unbounded`), and the store is not recorded in the ever-pinned index,
18
+ * so a deleted TS-written store reads as a first run.
19
+ */
20
+ export type ContextOutcome = "pinned" | "unchanged" | "numeric-unbounded" | "drifted" | "cosmetic" | "unobserved" | "tainted" | "skewed";
21
+ export interface ContextObservation {
22
+ readonly outcome: ContextOutcome;
23
+ readonly serverId: string;
24
+ readonly kinds: readonly string[];
25
+ readonly safetyRelevant: boolean;
26
+ readonly detail: string;
27
+ }
28
+ interface Leg {
29
+ status: string;
30
+ text: string | null;
31
+ text_dropped: boolean;
32
+ hash: string | null;
33
+ len: number | null;
34
+ }
35
+ interface ContextPinJson {
36
+ server_id: string;
37
+ pinned_at: string;
38
+ via: string;
39
+ protocol_version: string | null;
40
+ observed_via: string | null;
41
+ legs: Record<string, Leg>;
42
+ observed: Record<string, Leg>;
43
+ observed_at: string | null;
44
+ }
45
+ /** `.<stem>.context.json` beside the tool pin file (Python `default_context_store_path`). */
46
+ export declare function defaultContextStorePath(pinStorePath: string): string;
47
+ /** The instructions leg as Python `_instructions_from` shapes it: the hash is over
48
+ * the full UTF-8 text, the length counts code points, and text over the cap is
49
+ * dropped, never truncated. */
50
+ export declare function legFromInstructions(raw: unknown): Leg;
51
+ /** Classify one observation against the pin (Python `observe`). Pure. */
52
+ export declare function classifyInstructions(pinned: ContextPinJson | null, observed: Leg, serverId: string, observedVia: string, storeTainted: boolean): ContextObservation;
53
+ /** Owns one server's context pin and turns observations into reports. */
54
+ export declare class ContextWatcher {
55
+ private readonly serverId;
56
+ private readonly onEvent;
57
+ private readonly now;
58
+ private readonly store;
59
+ private storeError;
60
+ constructor(path: string, serverId: string, onEvent: (obs: ContextObservation) => void, now: () => string);
61
+ get path(): string | null;
62
+ observe(instructions: unknown, observedVia: string, protocolVersion: string | null): ContextObservation;
63
+ private emit;
64
+ }
65
+ export {};
66
+ //# sourceMappingURL=contextWatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextWatch.d.ts","sourceRoot":"","sources":["../../src/contextWatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAwBH,MAAM,MAAM,cAAc,GACtB,QAAQ,GACR,WAAW,GACX,mBAAmB,GACnB,SAAS,GACT,UAAU,GACV,YAAY,GACZ,SAAS,GACT,QAAQ,CAAC;AAEb,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,GAAG;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,6FAA6F;AAC7F,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAIpE;AAiBD;;+BAE+B;AAC/B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,CAQrD;AAsJD,yEAAyE;AACzE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,GAAG,IAAI,EAC7B,QAAQ,EAAE,GAAG,EACb,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,OAAO,GACpB,kBAAkB,CAyBpB;AAED,yEAAyE;AACzE,qBAAa,cAAc;IAMvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,UAAU,CAAuB;gBAGvC,IAAI,EAAE,MAAM,EACK,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,EAC1C,GAAG,EAAE,MAAM,MAAM;IAWpC,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,CAExB;IAED,OAAO,CAAC,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,kBAAkB;IAsCvG,OAAO,CAAC,IAAI;CAQb"}
@@ -0,0 +1,328 @@
1
+ /**
2
+ * The server's `instructions`, observed and pinned (port of Python
3
+ * `context_observe` / `context_pin`, observation only).
4
+ *
5
+ * MCP hosts put a server's `instructions` into the agent's context on connect.
6
+ * The tool hash does not cover them, so a server can keep every tool identical
7
+ * and rewrite the text the model reads first. The wrapper reads them after
8
+ * connect, pins them on first sight beside the tool pins, and reports a change.
9
+ * It never changes what `getInstructions()` returns: stripping the text removes
10
+ * the server's own guidance, replaying a pinned copy freezes first-connect text,
11
+ * and holding calls is a denial of service whoever controls the text can aim.
12
+ *
13
+ * The file is the same format the Python store writes
14
+ * (`.<stem>.context.json`, schema_version 1, a map of server id to pin), so a
15
+ * proxy and a wrapper on one server share a baseline. Limits against Python: no
16
+ * digit-budget carve-out (a numbers-only change is reported as
17
+ * `numeric-unbounded`), and the store is not recorded in the ever-pinned index,
18
+ * so a deleted TS-written store reads as a first run.
19
+ */
20
+ import { createHash, randomBytes } from "node:crypto";
21
+ import { closeSync, constants, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "node:fs";
22
+ import { basename, dirname, join } from "node:path";
23
+ import { ChangeKind, isNumericOnlyDescriptionChange } from "./schemaDiff.js";
24
+ const CONTEXT_STORE_VERSION = 1;
25
+ const MAX_INSTRUCTIONS_CHARS = 16_384;
26
+ const LEG = "instructions";
27
+ const SAFETY_KINDS = new Set([ChangeKind.INSTRUCTIONS_ADDED, ChangeKind.INSTRUCTIONS_CHANGED]);
28
+ /** `.<stem>.context.json` beside the tool pin file (Python `default_context_store_path`). */
29
+ export function defaultContextStorePath(pinStorePath) {
30
+ let stem = basename(pinStorePath);
31
+ if (stem.endsWith(".json"))
32
+ stem = stem.slice(0, -".json".length);
33
+ return join(dirname(pinStorePath) || ".", `.${stem}.context.json`);
34
+ }
35
+ /** Code points in `s` without building an array: a spread of a very long string can
36
+ * exhaust the heap, which no try/catch survives. */
37
+ function codePointLength(s) {
38
+ let n = 0;
39
+ for (let i = 0; i < s.length; i++) {
40
+ const c = s.charCodeAt(i);
41
+ if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) {
42
+ const d = s.charCodeAt(i + 1);
43
+ if (d >= 0xdc00 && d <= 0xdfff)
44
+ i++;
45
+ }
46
+ n++;
47
+ }
48
+ return n;
49
+ }
50
+ /** The instructions leg as Python `_instructions_from` shapes it: the hash is over
51
+ * the full UTF-8 text, the length counts code points, and text over the cap is
52
+ * dropped, never truncated. */
53
+ export function legFromInstructions(raw) {
54
+ if (typeof raw !== "string" || raw.length === 0) {
55
+ return { status: "ok", text: null, text_dropped: false, hash: null, len: null };
56
+ }
57
+ const len = codePointLength(raw);
58
+ const hash = "sha256:" + createHash("sha256").update(Buffer.from(raw, "utf8")).digest("hex");
59
+ const text = len <= MAX_INSTRUCTIONS_CHARS ? raw : null;
60
+ return { status: "ok", text, text_dropped: text === null, hash, len };
61
+ }
62
+ function parseLeg(raw) {
63
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
64
+ return null;
65
+ const r = raw;
66
+ if (typeof r["status"] !== "string")
67
+ return null;
68
+ const ln = r["len"];
69
+ return {
70
+ status: r["status"],
71
+ text: typeof r["text"] === "string" ? r["text"] : null,
72
+ text_dropped: r["text_dropped"] === true,
73
+ hash: typeof r["hash"] === "string" ? r["hash"] : null,
74
+ len: Number.isInteger(ln) && ln >= 0 ? ln : null,
75
+ };
76
+ }
77
+ function parsePin(raw) {
78
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
79
+ return null;
80
+ const r = raw;
81
+ const sid = r["server_id"];
82
+ const at = r["pinned_at"];
83
+ if (typeof sid !== "string" || sid.length === 0 || typeof at !== "string")
84
+ return null;
85
+ const legs = {};
86
+ const rawLegs = r["legs"];
87
+ if (rawLegs !== undefined && rawLegs !== null) {
88
+ if (typeof rawLegs !== "object" || Array.isArray(rawLegs))
89
+ return null;
90
+ for (const [name, value] of Object.entries(rawLegs)) {
91
+ const leg = parseLeg(value);
92
+ if (leg === null)
93
+ return null; // a pin with a hole in it would re-pin
94
+ legs[name] = leg;
95
+ }
96
+ }
97
+ const observed = {};
98
+ const rawObs = r["observed"];
99
+ if (rawObs !== null && typeof rawObs === "object" && !Array.isArray(rawObs)) {
100
+ for (const [name, value] of Object.entries(rawObs)) {
101
+ const leg = parseLeg(value);
102
+ if (leg === null)
103
+ return null;
104
+ observed[name] = leg;
105
+ }
106
+ }
107
+ const str = (v) => (typeof v === "string" ? v : null);
108
+ return {
109
+ server_id: sid,
110
+ pinned_at: at,
111
+ via: r["via"] === "manual" ? "manual" : "tofu",
112
+ protocol_version: str(r["protocol_version"]),
113
+ observed_via: str(r["observed_via"]),
114
+ legs,
115
+ observed,
116
+ observed_at: str(r["observed_at"]),
117
+ };
118
+ }
119
+ /** One stem's context pins. Unreadable, non-regular or newer-format files are
120
+ * tainted and never written. */
121
+ class ContextStore {
122
+ path;
123
+ pins = new Map();
124
+ tainted = null;
125
+ constructor(path) {
126
+ this.path = path;
127
+ this.load();
128
+ }
129
+ regular() {
130
+ try {
131
+ return lstatSync(this.path).isFile();
132
+ }
133
+ catch {
134
+ return null; // absent
135
+ }
136
+ }
137
+ readDisk() {
138
+ const regular = this.regular();
139
+ if (regular === null)
140
+ return new Map();
141
+ if (!regular) {
142
+ this.tainted = "context store is not a regular file";
143
+ return null;
144
+ }
145
+ let raw;
146
+ try {
147
+ raw = JSON.parse(readFileSync(this.path, "utf8"));
148
+ }
149
+ catch {
150
+ this.tainted = "context store is unreadable";
151
+ return null;
152
+ }
153
+ if (raw === null || typeof raw !== "object" || raw["schema_version"] !== CONTEXT_STORE_VERSION) {
154
+ this.tainted = "context store has an unsupported format";
155
+ return null;
156
+ }
157
+ const out = new Map();
158
+ const servers = raw["servers"];
159
+ if (servers !== null && typeof servers === "object" && !Array.isArray(servers)) {
160
+ for (const [key, value] of Object.entries(servers)) {
161
+ const pin = parsePin(value);
162
+ // A record filed under another server's key could overwrite that server's
163
+ // baseline on the next write. Python taints the same case.
164
+ if (pin === null || pin.server_id !== key) {
165
+ this.tainted = "context store holds an unreadable record";
166
+ continue;
167
+ }
168
+ out.set(key, pin);
169
+ }
170
+ }
171
+ return out;
172
+ }
173
+ load() {
174
+ const disk = this.readDisk();
175
+ if (disk !== null)
176
+ this.pins = disk;
177
+ }
178
+ get(serverId) {
179
+ return this.pins.get(serverId) ?? null;
180
+ }
181
+ put(pin) {
182
+ if (this.tainted !== null)
183
+ return;
184
+ const disk = this.readDisk();
185
+ if (disk === null)
186
+ return;
187
+ for (const [key, value] of disk)
188
+ if (!this.pins.has(key))
189
+ this.pins.set(key, value);
190
+ this.pins.set(pin.server_id, pin);
191
+ const servers = {};
192
+ for (const key of [...this.pins.keys()].sort())
193
+ servers[key] = this.pins.get(key);
194
+ const body = JSON.stringify({ schema_version: CONTEXT_STORE_VERSION, servers }, null, 2) + "\n";
195
+ const directory = dirname(this.path) || ".";
196
+ mkdirSync(directory, { recursive: true });
197
+ const tmp = join(directory, `.ctxpin.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
198
+ const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
199
+ const fd = openSync(tmp, flags, 0o600);
200
+ try {
201
+ writeSync(fd, body);
202
+ fsyncSync(fd);
203
+ }
204
+ finally {
205
+ closeSync(fd);
206
+ }
207
+ try {
208
+ renameSync(tmp, this.path);
209
+ }
210
+ catch (exc) {
211
+ try {
212
+ unlinkSync(tmp);
213
+ }
214
+ catch {
215
+ // already gone
216
+ }
217
+ throw exc;
218
+ }
219
+ }
220
+ }
221
+ /** Classify one observation against the pin (Python `observe`). Pure. */
222
+ export function classifyInstructions(pinned, observed, serverId, observedVia, storeTainted) {
223
+ const obs = (outcome, kinds = [], detail = "") => ({
224
+ outcome,
225
+ serverId,
226
+ kinds,
227
+ safetyRelevant: kinds.some((k) => SAFETY_KINDS.has(k)),
228
+ detail,
229
+ });
230
+ if (storeTainted)
231
+ return obs("tainted", [], "context store unreadable");
232
+ const leg = pinned?.legs[LEG] ?? null;
233
+ if (leg === null)
234
+ return obs("pinned");
235
+ if (leg.hash === observed.hash)
236
+ return obs("unchanged");
237
+ let kind;
238
+ if (leg.hash === null)
239
+ kind = ChangeKind.INSTRUCTIONS_ADDED;
240
+ else if (observed.hash === null)
241
+ kind = ChangeKind.INSTRUCTIONS_REMOVED;
242
+ else if (leg.text !== null && observed.text !== null && isNumericOnlyDescriptionChange(leg.text, observed.text)) {
243
+ kind = ChangeKind.INSTRUCTIONS_NUMERIC;
244
+ }
245
+ else
246
+ kind = ChangeKind.INSTRUCTIONS_CHANGED;
247
+ if (pinned !== null && pinned.observed_via !== null && pinned.observed_via !== observedVia) {
248
+ return obs("skewed", [kind], `pinned via ${pinned.observed_via}, observed via ${observedVia}`);
249
+ }
250
+ if (kind === ChangeKind.INSTRUCTIONS_NUMERIC) {
251
+ return obs("numeric-unbounded", [kind], "numeric-only change; this SDK does not apply the digit budget");
252
+ }
253
+ return obs(SAFETY_KINDS.has(kind) ? "drifted" : "cosmetic", [kind]);
254
+ }
255
+ /** Owns one server's context pin and turns observations into reports. */
256
+ export class ContextWatcher {
257
+ serverId;
258
+ onEvent;
259
+ now;
260
+ store;
261
+ storeError = null;
262
+ constructor(path, serverId, onEvent, now) {
263
+ this.serverId = serverId;
264
+ this.onEvent = onEvent;
265
+ this.now = now;
266
+ let store = null;
267
+ try {
268
+ store = new ContextStore(path);
269
+ }
270
+ catch (exc) {
271
+ this.storeError = exc instanceof Error ? exc.name : "Error";
272
+ }
273
+ this.store = store;
274
+ }
275
+ get path() {
276
+ return this.store?.path ?? null;
277
+ }
278
+ observe(instructions, observedVia, protocolVersion) {
279
+ const leg = legFromInstructions(instructions);
280
+ if (this.store === null) {
281
+ const failed = {
282
+ outcome: "unobserved",
283
+ serverId: this.serverId,
284
+ kinds: [],
285
+ safetyRelevant: false,
286
+ detail: this.storeError ?? "context store unavailable",
287
+ };
288
+ this.emit(failed);
289
+ return failed;
290
+ }
291
+ const pin = this.store.get(this.serverId);
292
+ const result = classifyInstructions(pin, leg, this.serverId, observedVia, this.store.tainted !== null);
293
+ try {
294
+ if (result.outcome === "pinned") {
295
+ this.store.put({
296
+ server_id: this.serverId,
297
+ pinned_at: this.now(),
298
+ via: "tofu",
299
+ protocol_version: protocolVersion,
300
+ observed_via: observedVia,
301
+ legs: { [LEG]: leg },
302
+ observed: {},
303
+ observed_at: null,
304
+ });
305
+ }
306
+ else if (pin !== null && result.outcome !== "unchanged" && result.outcome !== "tainted") {
307
+ // Record what was seen without moving the baseline.
308
+ this.store.put({ ...pin, observed: { [LEG]: leg }, observed_at: this.now() });
309
+ }
310
+ }
311
+ catch {
312
+ // A failed write costs the record, never the session.
313
+ }
314
+ this.emit(result);
315
+ return result;
316
+ }
317
+ emit(obs) {
318
+ if (obs.outcome === "unchanged")
319
+ return;
320
+ try {
321
+ this.onEvent(obs);
322
+ }
323
+ catch {
324
+ // a report must never perturb the session
325
+ }
326
+ }
327
+ }
328
+ //# sourceMappingURL=contextWatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextWatch.js","sourceRoot":"","sources":["../../src/contextWatch.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@
12
12
  * pin a key it lacks was never recorded, so it is filled in from the first
13
13
  * matching list instead of read as a change.
14
14
  */
15
- export declare const CONTRACT_FIELDS_VERSION = 2;
15
+ export declare const CONTRACT_FIELDS_VERSION = 3;
16
16
  /** Where a pin file keeps the marker: inside `schema`, which gate 0.18 and SDK
17
17
  * 0.16 keep whole when they rewrite a record. The store moves it out on load and
18
18
  * back on write; `extraFields` skips it so a server cannot supply it. */
@@ -26,6 +26,10 @@ export declare function assignOwn(target: Record<string, unknown>, source: Recor
26
26
  * matching what pins written by 0.16 stored. */
27
27
  export declare function extraFields(entry: unknown): Record<string, unknown>;
28
28
  export declare const ANNOTATION_TEXT_KEY = "annotations";
29
+ /** What a normalized definition and a pin keep: `extraFields` plus a title that
30
+ * is present but not a string. The digest stays on `extraFields`. Mirrors Python
31
+ * `listed_fields`. */
32
+ export declare function listedFields(entry: unknown): Record<string, unknown>;
29
33
  /** What the gate compares on a hash match: the extra fields, plus the
30
34
  * annotation keys other than the four hints under one synthetic key that never
31
35
  * reaches a pin or a digest. Mirrors Python `compared_fields`. */
@@ -36,6 +40,11 @@ export declare function comparedFields(entry: unknown): Record<string, unknown>;
36
40
  export declare function comparedKeys(pinnedSchema: Record<string, unknown>, observed: Record<string, unknown>, complete: boolean): string[];
37
41
  /** Extra fields the live entry has and an old pin never recorded. */
38
42
  export declare function missingExtraFields(pinnedSchema: Record<string, unknown>, observed: Record<string, unknown>): Record<string, unknown>;
43
+ /** Whether a pin's marker means it recorded every field listed at the time. */
44
+ export declare function isComplete(version: number | undefined): boolean;
45
+ /** What a pin at `version` may record once from a matching list (Python
46
+ * `fields_to_backfill`): everything it lacks when unmarked, nothing when marked. */
47
+ export declare function fieldsToBackfill(pinnedSchema: Record<string, unknown>, observed: Record<string, unknown>, version: number | undefined): Record<string, unknown>;
39
48
  /** A server-chosen key, quoted and escaped for a reason string (Python
40
49
  * `json.dumps(key, ensure_ascii=True)`). */
41
50
  export declare function fieldLabel(key: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"contractFields.d.ts","sourceRoot":"","sources":["../../src/contractFields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;yEAEyE;AACzE,eAAO,MAAM,iBAAiB,8BAA8B,CAAC;AAE7D;uDACuD;AACvD,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAEtF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEhG;AAKD;;gDAEgD;AAChD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUnE;AAOD,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD;;kEAEkE;AAClE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAatE;AAED;;wBAEwB;AACxB,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,OAAO,GAChB,MAAM,EAAE,CAOV;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB;AAED;4CAC4C;AAC5C,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C"}
1
+ {"version":3,"file":"contractFields.d.ts","sourceRoot":"","sources":["../../src/contractFields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;yEAEyE;AACzE,eAAO,MAAM,iBAAiB,8BAA8B,CAAC;AAE7D;uDACuD;AACvD,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAEtF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEhG;AAKD;;gDAEgD;AAChD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUnE;AAOD,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD;;sBAEsB;AACtB,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMpE;AAED;;kEAEkE;AAClE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAatE;AAED;;wBAEwB;AACxB,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,OAAO,GAChB,MAAM,EAAE,CAOV;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB;AAKD,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAG/D;AAED;oFACoF;AACpF,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,EAAE,MAAM,GAAG,SAAS,GAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIzB;AAED;4CAC4C;AAC5C,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C"}
@@ -12,7 +12,8 @@
12
12
  * pin a key it lacks was never recorded, so it is filled in from the first
13
13
  * matching list instead of read as a change.
14
14
  */
15
- export const CONTRACT_FIELDS_VERSION = 2;
15
+ // 3 since a non-string title is compared. Any marker from 2 up is complete.
16
+ export const CONTRACT_FIELDS_VERSION = 3;
16
17
  /** Where a pin file keeps the marker: inside `schema`, which gate 0.18 and SDK
17
18
  * 0.16 keep whole when they rewrite a record. The store moves it out on load and
18
19
  * back on write; `extraFields` skips it so a server cannot supply it. */
@@ -52,11 +53,23 @@ const ANNOTATION_HINTS = new Set(["readOnlyHint", "destructiveHint", "idempotent
52
53
  // "annotations" itself: `extraFields` never carries a top-level "annotations",
53
54
  // so no server key can collide with it.
54
55
  export const ANNOTATION_TEXT_KEY = "annotations";
56
+ /** What a normalized definition and a pin keep: `extraFields` plus a title that
57
+ * is present but not a string. The digest stays on `extraFields`. Mirrors Python
58
+ * `listed_fields`. */
59
+ export function listedFields(entry) {
60
+ const out = extraFields(entry);
61
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry))
62
+ return out;
63
+ const title = entry["title"];
64
+ if (title !== undefined && title !== null && typeof title !== "string")
65
+ setOwn(out, "title", title);
66
+ return out;
67
+ }
55
68
  /** What the gate compares on a hash match: the extra fields, plus the
56
69
  * annotation keys other than the four hints under one synthetic key that never
57
70
  * reaches a pin or a digest. Mirrors Python `compared_fields`. */
58
71
  export function comparedFields(entry) {
59
- const out = extraFields(entry);
72
+ const out = listedFields(entry);
60
73
  if (entry === null || typeof entry !== "object" || Array.isArray(entry))
61
74
  return out;
62
75
  const ann = entry["annotations"];
@@ -86,7 +99,7 @@ export function comparedKeys(pinnedSchema, observed, complete) {
86
99
  }
87
100
  /** Extra fields the live entry has and an old pin never recorded. */
88
101
  export function missingExtraFields(pinnedSchema, observed) {
89
- const have = new Set(Object.keys(extraFields(pinnedSchema)));
102
+ const have = new Set(Object.keys(listedFields(pinnedSchema)));
90
103
  const out = {};
91
104
  for (const [key, value] of Object.entries(extraFields(observed))) {
92
105
  if (!have.has(key))
@@ -94,6 +107,21 @@ export function missingExtraFields(pinnedSchema, observed) {
94
107
  }
95
108
  return out;
96
109
  }
110
+ // The first marker version that recorded every listed field.
111
+ const FIRST_COMPLETE_VERSION = 2;
112
+ /** Whether a pin's marker means it recorded every field listed at the time. */
113
+ export function isComplete(version) {
114
+ // No upper bound: a newer writer records a superset.
115
+ return version !== undefined && Number.isInteger(version) && version >= FIRST_COMPLETE_VERSION;
116
+ }
117
+ /** What a pin at `version` may record once from a matching list (Python
118
+ * `fields_to_backfill`): everything it lacks when unmarked, nothing when marked. */
119
+ export function fieldsToBackfill(pinnedSchema, observed, version) {
120
+ // A marked pin records nothing; a non-string title is never recorded.
121
+ if (!isComplete(version))
122
+ return missingExtraFields(pinnedSchema, observed);
123
+ return {};
124
+ }
97
125
  /** A server-chosen key, quoted and escaped for a reason string (Python
98
126
  * `json.dumps(key, ensure_ascii=True)`). */
99
127
  export function fieldLabel(key) {