@watchlight/sdk 0.1.0 → 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.
package/README.md CHANGED
@@ -81,6 +81,33 @@ for await (const msg of query({ prompt, options: { hooks } })) {
81
81
  The hook is fail-closed and never throws back to the SDK — a governance error
82
82
  denies the call. Every decision is audited.
83
83
 
84
+ ## LangChain / LangGraph.js
85
+
86
+ Govern any LangChain `StructuredTool` (which is what LangGraph.js tools are) — the
87
+ tool is authorized before it runs; denied tools throw and never execute.
88
+
89
+ ```ts
90
+ import { tool } from "@langchain/core/tools";
91
+ import { z } from "zod";
92
+ import { govern, governTool } from "@watchlight/sdk";
93
+
94
+ govern.load("watchlight.policy.json");
95
+
96
+ const search = governTool(
97
+ tool(async ({ query }) => webSearch(query), {
98
+ name: "web_search",
99
+ schema: z.object({ query: z.string() }),
100
+ }),
101
+ { intent: "research" }
102
+ );
103
+
104
+ // Pass `search` to your LangGraph ToolNode / createReactAgent as usual.
105
+ ```
106
+
107
+ `governTool(tool, { intent })` returns a governed view (the original tool isn't
108
+ mutated); `governTools(tools, { intentFor })` maps an array. Intent defaults to
109
+ the tool's name. Fail-closed. `@langchain/core` is a peer dependency.
110
+
84
111
  ## Value-free audit
85
112
 
86
113
  `.watchlight/audit.jsonl` records **who / what intent / which tool / the
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ import { Scope } from "./attenuation";
2
2
  export { Scope, DE_MAX_DEPTH, AttenuationDenied, DevEditionCeiling } from "./attenuation";
3
3
  export { governedHooks } from "./claude-agent";
4
4
  export type { GovernedHooksOptions, GovernedHooksResult } from "./claude-agent";
5
+ export { governTool, governTools } from "./langchain";
6
+ export type { LangChainToolLike, GovernToolOptions, GovernToolsOptions, } from "./langchain";
5
7
  export type { GovernanceBackend, Decision, AuthorizeRequest } from "./backend";
6
8
  export { InProcessBackend, NetworkedBackend } from "./backend";
7
9
  /** Raised when the policy engine refuses a governed tool call (fail-closed). */
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ var __importStar = (this && this.__importStar) || (function () {
49
49
  };
50
50
  })();
51
51
  Object.defineProperty(exports, "__esModule", { value: true });
52
- exports.govern = exports.Watchlight = exports.Denied = exports.NetworkedBackend = exports.InProcessBackend = exports.governedHooks = exports.DevEditionCeiling = exports.AttenuationDenied = exports.DE_MAX_DEPTH = exports.Scope = void 0;
52
+ exports.govern = exports.Watchlight = exports.Denied = exports.NetworkedBackend = exports.InProcessBackend = exports.governTools = exports.governTool = exports.governedHooks = exports.DevEditionCeiling = exports.AttenuationDenied = exports.DE_MAX_DEPTH = exports.Scope = void 0;
53
53
  const fs = __importStar(require("node:fs"));
54
54
  const path = __importStar(require("node:path"));
55
55
  const attenuation_1 = require("./attenuation");
@@ -61,6 +61,9 @@ Object.defineProperty(exports, "AttenuationDenied", { enumerable: true, get: fun
61
61
  Object.defineProperty(exports, "DevEditionCeiling", { enumerable: true, get: function () { return attenuation_2.DevEditionCeiling; } });
62
62
  var claude_agent_1 = require("./claude-agent");
63
63
  Object.defineProperty(exports, "governedHooks", { enumerable: true, get: function () { return claude_agent_1.governedHooks; } });
64
+ var langchain_1 = require("./langchain");
65
+ Object.defineProperty(exports, "governTool", { enumerable: true, get: function () { return langchain_1.governTool; } });
66
+ Object.defineProperty(exports, "governTools", { enumerable: true, get: function () { return langchain_1.governTools; } });
64
67
  var backend_2 = require("./backend");
65
68
  Object.defineProperty(exports, "InProcessBackend", { enumerable: true, get: function () { return backend_2.InProcessBackend; } });
66
69
  Object.defineProperty(exports, "NetworkedBackend", { enumerable: true, get: function () { return backend_2.NetworkedBackend; } });
@@ -0,0 +1,32 @@
1
+ import { Watchlight } from "./index";
2
+ /** The minimal shape of a LangChain `StructuredTool` this adapter needs. */
3
+ export interface LangChainToolLike {
4
+ name: string;
5
+ invoke(input: unknown, config?: unknown): Promise<unknown>;
6
+ [k: string]: unknown;
7
+ }
8
+ export interface GovernToolOptions {
9
+ /** The governor to authorize against. Defaults to the shared `govern`. */
10
+ governor?: Watchlight;
11
+ /** Governance intent for this tool. Defaults to the tool's `name`. */
12
+ intent?: string;
13
+ }
14
+ export interface GovernToolsOptions {
15
+ governor?: Watchlight;
16
+ /** Map a tool name to a governance intent. Defaults to identity (intent =
17
+ * tool name). */
18
+ intentFor?: (toolName: string) => string;
19
+ }
20
+ /**
21
+ * Wrap a LangChain / LangGraph.js tool so its `invoke` is authorized by the
22
+ * in-process engine before it runs. Returns a governed view of the tool (a
23
+ * Proxy) — pass it to your agent / `ToolNode` exactly like the original. The
24
+ * original tool is not mutated. Fail-closed: on anything but ALLOW, `invoke`
25
+ * throws `Denied` and the underlying tool never executes.
26
+ */
27
+ export declare function governTool<T extends LangChainToolLike>(tool: T, opts?: GovernToolOptions): T;
28
+ /**
29
+ * Govern an array of LangChain / LangGraph.js tools. `intentFor` maps each tool
30
+ * name to an intent (default: the tool name).
31
+ */
32
+ export declare function governTools<T extends LangChainToolLike>(tools: T[], opts?: GovernToolsOptions): T[];
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ // LangChain / LangGraph.js integration — govern a tool's execution with the
3
+ // in-process engine. The TS counterpart of Python `watchlight.langgraph`.
4
+ //
5
+ // import { tool } from "@langchain/core/tools";
6
+ // import { govern, governTool } from "@watchlight/sdk";
7
+ //
8
+ // govern.load("watchlight.policy.json");
9
+ // const search = governTool(
10
+ // tool(async ({ query }) => webSearch(query), { name: "web_search", schema }),
11
+ // { intent: "research" }
12
+ // );
13
+ // // pass `search` to your LangGraph agent / ToolNode as usual.
14
+ //
15
+ // Before the tool runs, the engine authorizes (agent, intent, tool/<name>). ALLOW
16
+ // runs it; anything else throws `Denied` and the tool body never executes —
17
+ // denied before it runs. Fail-closed. Works for any LangChain `StructuredTool`
18
+ // (which is what LangGraph.js tools are).
19
+ //
20
+ // This is glue: it intercepts the tool's `invoke`; the decision comes from the
21
+ // engine (via the shared governor). No LangChain hard dependency — the adapter is
22
+ // structurally typed against the tool's public shape, so `@langchain/core` stays
23
+ // a peer you already have installed.
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.governTool = governTool;
26
+ exports.governTools = governTools;
27
+ const index_1 = require("./index");
28
+ /**
29
+ * Wrap a LangChain / LangGraph.js tool so its `invoke` is authorized by the
30
+ * in-process engine before it runs. Returns a governed view of the tool (a
31
+ * Proxy) — pass it to your agent / `ToolNode` exactly like the original. The
32
+ * original tool is not mutated. Fail-closed: on anything but ALLOW, `invoke`
33
+ * throws `Denied` and the underlying tool never executes.
34
+ */
35
+ function governTool(tool, opts = {}) {
36
+ const governor = opts.governor ?? index_1.govern;
37
+ const intent = opts.intent ?? tool.name;
38
+ const name = tool.name;
39
+ return new Proxy(tool, {
40
+ get(target, prop, receiver) {
41
+ if (prop === "invoke") {
42
+ return async (input, config) => {
43
+ const { allowed, reason } = await governor.check(intent, name);
44
+ if (!allowed) {
45
+ throw new index_1.Denied(name, intent, reason || "no matching policy");
46
+ }
47
+ return target.invoke(input, config);
48
+ };
49
+ }
50
+ // Delegate everything else to the real tool, bound to it so `this` stays
51
+ // correct and internal calls hit the real (un-governed) methods.
52
+ const value = Reflect.get(target, prop, receiver);
53
+ return typeof value === "function" ? value.bind(target) : value;
54
+ },
55
+ });
56
+ }
57
+ /**
58
+ * Govern an array of LangChain / LangGraph.js tools. `intentFor` maps each tool
59
+ * name to an intent (default: the tool name).
60
+ */
61
+ function governTools(tools, opts = {}) {
62
+ const intentFor = opts.intentFor ?? ((n) => n);
63
+ return tools.map((t) => governTool(t, { governor: opts.governor, intent: intentFor(t.name) }));
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@watchlight/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Watchlight Developer Edition govern glue for Node/TypeScript — declare intent, govern a tool with a fail-closed in-process policy decision, and get a value-free audit trail. Glue over @watchlight/engine; zero decision logic in JS.",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "scripts": {
17
17
  "build": "tsc -p tsconfig.json",
18
- "test": "npm run build && node test/govern.test.mjs && node test/claude-agent.test.mjs && node test/graduation.test.mjs",
18
+ "test": "npm run build && node test/govern.test.mjs && node test/claude-agent.test.mjs && node test/graduation.test.mjs && node test/langchain.test.mjs",
19
19
  "prepublishOnly": "npm run build"
20
20
  },
21
21
  "keywords": [