@haikeilabs/agentware 0.4.0 → 0.5.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
@@ -44,6 +44,50 @@ Keep channel ingress, identity verification, delegated credentials, and
44
44
  domain-specific authorization in the host application. Agentware is not a
45
45
  policy engine; a policy gate can wrap this same boundary later.
46
46
 
47
+ ## kei-proxy policy evaluator
48
+
49
+ `KeiProxyEvaluator` runs `kei-proxy authorize` before each tool call and fails
50
+ closed on anything other than an explicit `allow`/`permit` with exit 0.
51
+ `evaluate` is asynchronous: the event loop keeps serving other requests while
52
+ kei-proxy answers. Await the decision before running the tool.
53
+
54
+ `KeiProxyAuthorizeClient` spawns the binary by absolute path (a bare name is
55
+ resolved once against the parent `PATH`) with an allowlisted environment only:
56
+ `KEI_RUNTIME_TOKEN`, `KEI_RUNTIME_CONTROL_PLANE_URL`, `KEI_RUNTIME_VERSION` and
57
+ the other `AUTHORIZE_CHILD_ENV_ALLOWLIST` names. No `PATH`, no `HOME`, and the
58
+ token never goes in argv. The child runs in its own process group, and a
59
+ timeout SIGKILLs the whole group. Set `expectedSha256` to pin the binary: before
60
+ every spawn it is opened with `O_NOFOLLOW` and re-hashed, and any drift denies
61
+ with `kei-proxy pin_mismatch`.
62
+
63
+ ```typescript
64
+ import { Action, KeiProxyAuthorizeClient, KeiProxyEvaluator } from "@haikeilabs/agentware";
65
+
66
+ const evaluator = new KeiProxyEvaluator(
67
+ new KeiProxyAuthorizeClient({
68
+ executable: "/usr/local/bin/kei-proxy",
69
+ expectedSha256: process.env.KEI_PROXY_SHA256, // optional pin
70
+ timeoutMs: 10_000,
71
+ }),
72
+ );
73
+
74
+ const decision = await evaluator.evaluate(
75
+ "github.get_issue",
76
+ { owner: "acme", repo: "pipe" },
77
+ caller,
78
+ );
79
+ if (decision.enrollment) {
80
+ // deny + enrollment claim link: reply to the user privately; never log it.
81
+ }
82
+ if (decision.action !== Action.ALLOW) return;
83
+ ```
84
+
85
+ Reasons read `kei-proxy <class>[: <detail>]` with a class from
86
+ `KEI_PROXY_REASON_CLASSES`. Because `evaluate` returns a `Promise`, the
87
+ evaluator is not a synchronous `PolicyEvaluator` and cannot be passed to
88
+ `MiddlewareImpl.withPolicy`. The behaviour matches the Python and Go ports
89
+ case for case; see `docs/kei-proxy-evaluator-parity.md`.
90
+
47
91
  ## Maintainer releases
48
92
 
49
93
  Releases are deliberately manual and use npm staged publishing. Do not run
@@ -0,0 +1,55 @@
1
+ import { type KeiProxyAuthorizationClient, type KeiProxyAuthorizeRequest } from "./evaluator.js";
2
+ /**
3
+ * Parent variables the kei-proxy child may inherit. PATH and HOME are
4
+ * deliberately absent, as are KEI_PROXY_* identity variables: identity
5
+ * travels as flags.
6
+ */
7
+ export declare const AUTHORIZE_CHILD_ENV_ALLOWLIST: readonly ["TZ", "KEI_RUNTIME_TOKEN", "KEI_RUNTIME_CONTROL_PLANE_URL", "KEI_RUNTIME_VERSION", "KEI_API_URL", "KEI_PROXY_REGISTRY", "KEI_PROXY_AUDIT", "KEI_PROXY_AUDIT_MAX_BYTES", "KEI_PROXY_PROVIDER_USER", "KEI_CREDENTIAL_STORE_INSTALLATION_ID", "KEI_AWS_SECRETS_MANAGER_ENDPOINT"];
8
+ export declare const DEFAULT_AUTHORIZE_TIMEOUT_MS = 10000;
9
+ export interface KeiProxyAuthorizeClientOptions {
10
+ /**
11
+ * Path or name of the kei-proxy binary. Default `kei-proxy`. A bare name is
12
+ * resolved once against the parent PATH (`env.PATH`, else `process.env.PATH`);
13
+ * the child is always spawned by absolute path.
14
+ */
15
+ executable?: string;
16
+ /**
17
+ * Expected SHA-256 of the binary (64 hex digits). When set, `executable` must
18
+ * be an absolute, symlink-free path, and the binary is re-hashed before
19
+ * every spawn; a mismatch denies with `pin_mismatch`.
20
+ */
21
+ expectedSha256?: string;
22
+ /** Milliseconds to wait for one authorize call. Default 10s. */
23
+ timeoutMs?: number;
24
+ /** Parent environment to draw allowlisted variables from. Default `process.env`. */
25
+ env?: Readonly<Record<string, string | undefined>>;
26
+ /** Variables passed to the child verbatim, for settings outside the allowlist. */
27
+ extraEnv?: Readonly<Record<string, string>>;
28
+ }
29
+ /**
30
+ * Runs `kei-proxy authorize` once per call without blocking the event loop.
31
+ * The runtime token reaches the child only through its environment, never argv.
32
+ */
33
+ export declare class KeiProxyAuthorizeClient implements KeiProxyAuthorizationClient {
34
+ /** Absolute path to spawn, or undefined when a bare name was not found. */
35
+ private readonly executable;
36
+ private readonly expectedDigest;
37
+ private readonly timeoutMs;
38
+ private readonly env?;
39
+ private readonly extraEnv;
40
+ constructor(options?: KeiProxyAuthorizeClientOptions);
41
+ /** The exact environment the kei-proxy child receives. */
42
+ childEnv(): Record<string, string>;
43
+ authorize(request: KeiProxyAuthorizeRequest): Promise<Record<string, unknown>>;
44
+ /** Spawn once in a new process group; resolve with stdout and exit code. */
45
+ private run;
46
+ }
47
+ /**
48
+ * The argv (after the executable) for one authorize call. `agentId` and
49
+ * `workspaceId` are not sent: kei-proxy (>= 0.1.11) resolves the agent from
50
+ * the runtime installation and workspace scope from the runtime token.
51
+ */
52
+ export declare function keiProxyAuthorizeArgv(request: KeiProxyAuthorizeRequest): string[];
53
+ /** Validate one authorize result against the exit-code contract. */
54
+ export declare function parseKeiProxyAuthorizeOutput(stdout: string, exitCode: number): Record<string, unknown>;
55
+ //# sourceMappingURL=authorizeClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorizeClient.d.ts","sourceRoot":"","sources":["../../src/kei/authorizeClient.ts"],"names":[],"mappings":"AAgCA,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC9B,MAAM,gBAAgB,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,0RAYhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAWnD,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACnD,kFAAkF;IAClF,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC7C;AAED;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,2BAA2B;IACzE,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAA+C;IACpE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;gBAEhD,OAAO,GAAE,8BAAmC;IAoBxD,0DAA0D;IAC1D,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAU5B,SAAS,CACb,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAyBnC,4EAA4E;IAC5E,OAAO,CAAC,GAAG;CAgGZ;AAyFD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,wBAAwB,GAChC,MAAM,EAAE,CA6BV;AAED,oEAAoE;AACpE,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA+CzB"}
@@ -0,0 +1,349 @@
1
+ /**
2
+ * `KeiProxyAuthorizeClient`: runs `kei-proxy authorize` for `KeiProxyEvaluator`.
3
+ *
4
+ * TypeScript port of `pedro_agentware.kei.authorize_client`. The kei-proxy
5
+ * contract (kei-connector-runtime `authorize.go`): stdout is one JSON object;
6
+ * exit 0 for allow / enrollment_required, exit 1 for deny, and exit 1 with
7
+ * nothing on stdout for every error.
8
+ *
9
+ * Security contract (`fixtures/kei/authorize-cases.v1.json`):
10
+ *
11
+ * - The child is spawned asynchronously, so the harness's event loop keeps
12
+ * running while kei-proxy answers.
13
+ * - The child environment is exactly the allowlisted parent variables plus
14
+ * explicit `extraEnv`: no PATH, no HOME. The executable is resolved to an
15
+ * absolute path in the parent, once, at construction.
16
+ * - With `expectedSha256` the binary is re-proved before every spawn: the
17
+ * path must equal its realpath, it is opened with O_NOFOLLOW, must be a
18
+ * regular file, and its SHA-256 is streamed from that descriptor and compared
19
+ * in constant time. Any drift denies (`pin_mismatch`) without spawning. What
20
+ * remains is the hash-then-exec race; close it by making the binary's
21
+ * directory and its ancestors writable only by the deploy owner.
22
+ * - The child runs in its own process group; a timeout or oversized stdout
23
+ * SIGKILLs the whole group.
24
+ * - Proxy stdout can carry a credential or an enrollment claim link and stderr
25
+ * is free-form, so stderr is never read and neither is ever logged or copied
26
+ * into an error message.
27
+ */
28
+ import { spawn } from "node:child_process";
29
+ import { createHash, timingSafeEqual } from "node:crypto";
30
+ import { accessSync, constants as fsConstants, statSync } from "node:fs";
31
+ import { open, realpath } from "node:fs/promises";
32
+ import { delimiter, isAbsolute, join, resolve } from "node:path";
33
+ import { KEI_PROXY_AFFIRMATIVE_DECISIONS, KeiProxyAuthorizeError, } from "./evaluator.js";
34
+ /**
35
+ * Parent variables the kei-proxy child may inherit. PATH and HOME are
36
+ * deliberately absent, as are KEI_PROXY_* identity variables: identity
37
+ * travels as flags.
38
+ */
39
+ export const AUTHORIZE_CHILD_ENV_ALLOWLIST = [
40
+ "TZ",
41
+ "KEI_RUNTIME_TOKEN",
42
+ "KEI_RUNTIME_CONTROL_PLANE_URL",
43
+ "KEI_RUNTIME_VERSION",
44
+ "KEI_API_URL",
45
+ "KEI_PROXY_REGISTRY",
46
+ "KEI_PROXY_AUDIT",
47
+ "KEI_PROXY_AUDIT_MAX_BYTES",
48
+ "KEI_PROXY_PROVIDER_USER",
49
+ "KEI_CREDENTIAL_STORE_INSTALLATION_ID",
50
+ "KEI_AWS_SECRETS_MANAGER_ENDPOINT",
51
+ ];
52
+ export const DEFAULT_AUTHORIZE_TIMEOUT_MS = 10_000;
53
+ /** Upper bound on captured stdout. */
54
+ const MAX_STDOUT_BYTES = 1024 * 1024;
55
+ /** Upper bound on the executable's size when digesting it. */
56
+ const MAX_EXECUTABLE_BYTES = 256 * 1024 * 1024;
57
+ const HASH_CHUNK_BYTES = 64 * 1024;
58
+ const SHA256_HEX = /^[0-9a-fA-F]{64}$/;
59
+ const RUNTIME_TOKEN_ENV = "KEI_RUNTIME_TOKEN";
60
+ /**
61
+ * Runs `kei-proxy authorize` once per call without blocking the event loop.
62
+ * The runtime token reaches the child only through its environment, never argv.
63
+ */
64
+ export class KeiProxyAuthorizeClient {
65
+ /** Absolute path to spawn, or undefined when a bare name was not found. */
66
+ executable;
67
+ expectedDigest;
68
+ timeoutMs;
69
+ env;
70
+ extraEnv;
71
+ constructor(options = {}) {
72
+ const executable = options.executable ?? "kei-proxy";
73
+ if (options.expectedSha256 !== undefined) {
74
+ if (!SHA256_HEX.test(options.expectedSha256)) {
75
+ throw new TypeError("expectedSha256 must be 64 hex digits");
76
+ }
77
+ if (!isAbsolute(executable)) {
78
+ throw new TypeError("a pinned executable must be an absolute path");
79
+ }
80
+ this.expectedDigest = Buffer.from(options.expectedSha256, "hex");
81
+ }
82
+ this.env = options.env;
83
+ this.executable = resolveExecutable(executable, (this.env ?? process.env).PATH);
84
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_AUTHORIZE_TIMEOUT_MS;
85
+ this.extraEnv = options.extraEnv ?? {};
86
+ }
87
+ /** The exact environment the kei-proxy child receives. */
88
+ childEnv() {
89
+ const source = this.env ?? process.env;
90
+ const out = {};
91
+ for (const key of AUTHORIZE_CHILD_ENV_ALLOWLIST) {
92
+ const value = source[key];
93
+ if (value !== undefined)
94
+ out[key] = value;
95
+ }
96
+ return { ...out, ...this.extraEnv };
97
+ }
98
+ async authorize(request) {
99
+ const env = this.childEnv();
100
+ if (!env[RUNTIME_TOKEN_ENV]) {
101
+ throw new KeiProxyAuthorizeError("missing_token", `${RUNTIME_TOKEN_ENV} is not set`);
102
+ }
103
+ if (this.executable === undefined) {
104
+ throw new KeiProxyAuthorizeError("proxy_unavailable", "kei-proxy was not found on PATH");
105
+ }
106
+ if (this.expectedDigest !== undefined) {
107
+ await proveExecutable(this.executable, this.expectedDigest);
108
+ }
109
+ const { stdout, exitCode } = await this.run(this.executable, keiProxyAuthorizeArgv(request), env);
110
+ return parseKeiProxyAuthorizeOutput(stdout, exitCode);
111
+ }
112
+ /** Spawn once in a new process group; resolve with stdout and exit code. */
113
+ run(executable, argv, env) {
114
+ return new Promise((resolvePromise, rejectPromise) => {
115
+ let child;
116
+ try {
117
+ child = spawn(executable, argv, {
118
+ env,
119
+ stdio: ["ignore", "pipe", "ignore"],
120
+ detached: process.platform !== "win32",
121
+ shell: false,
122
+ windowsHide: true,
123
+ });
124
+ }
125
+ catch (err) {
126
+ rejectPromise(unavailable(err));
127
+ return;
128
+ }
129
+ let settled = false;
130
+ const chunks = [];
131
+ let total = 0;
132
+ const killGroup = () => {
133
+ const pid = child.pid;
134
+ if (pid === undefined)
135
+ return;
136
+ try {
137
+ process.kill(process.platform === "win32" ? pid : -pid, "SIGKILL");
138
+ }
139
+ catch {
140
+ try {
141
+ child.kill("SIGKILL");
142
+ }
143
+ catch {
144
+ // Already gone.
145
+ }
146
+ }
147
+ };
148
+ const fail = (error, kill) => {
149
+ if (settled)
150
+ return;
151
+ settled = true;
152
+ clearTimeout(timer);
153
+ if (kill)
154
+ killGroup();
155
+ rejectPromise(error);
156
+ };
157
+ const timer = setTimeout(() => {
158
+ fail(new KeiProxyAuthorizeError("proxy_timeout", `no answer within ${this.timeoutMs}ms`), true);
159
+ }, this.timeoutMs);
160
+ child.on("error", (err) => {
161
+ if (child.pid === undefined) {
162
+ fail(unavailable(err), false);
163
+ }
164
+ else {
165
+ fail(new KeiProxyAuthorizeError("proxy_error", "kei-proxy failed"), true);
166
+ }
167
+ });
168
+ child.stdout?.on("data", (chunk) => {
169
+ if (settled)
170
+ return;
171
+ total += chunk.length;
172
+ if (total > MAX_STDOUT_BYTES) {
173
+ fail(new KeiProxyAuthorizeError("malformed_response", "stdout exceeded the size limit"), true);
174
+ return;
175
+ }
176
+ chunks.push(chunk);
177
+ });
178
+ child.on("close", (code, signal) => {
179
+ if (settled)
180
+ return;
181
+ settled = true;
182
+ clearTimeout(timer);
183
+ if (code === null) {
184
+ rejectPromise(new KeiProxyAuthorizeError("proxy_error", `kei-proxy terminated by ${signal ?? "signal"}`));
185
+ return;
186
+ }
187
+ resolvePromise({
188
+ stdout: Buffer.concat(chunks).toString("utf8"),
189
+ exitCode: code,
190
+ });
191
+ });
192
+ });
193
+ }
194
+ }
195
+ function unavailable(err) {
196
+ const code = err?.code;
197
+ return new KeiProxyAuthorizeError("proxy_unavailable", `could not start kei-proxy (${code ?? "error"})`);
198
+ }
199
+ /**
200
+ * An absolute path for `executable`. Names containing a separator resolve
201
+ * against the working directory; bare names are looked up in the absolute
202
+ * entries of `pathVar` (relative entries are skipped).
203
+ */
204
+ function resolveExecutable(executable, pathVar) {
205
+ if (isAbsolute(executable))
206
+ return executable;
207
+ if (executable.includes("/") || executable.includes("\\")) {
208
+ return resolve(executable);
209
+ }
210
+ for (const dir of (pathVar ?? "").split(delimiter)) {
211
+ if (!dir || !isAbsolute(dir))
212
+ continue;
213
+ const candidate = join(dir, executable);
214
+ try {
215
+ if (!statSync(candidate).isFile())
216
+ continue;
217
+ accessSync(candidate, fsConstants.X_OK);
218
+ return candidate;
219
+ }
220
+ catch {
221
+ // Not here; keep looking.
222
+ }
223
+ }
224
+ return undefined;
225
+ }
226
+ function pinMismatch(detail) {
227
+ return new KeiProxyAuthorizeError("pin_mismatch", detail);
228
+ }
229
+ /** Re-prove the pinned executable immediately before a spawn. */
230
+ async function proveExecutable(path, expected) {
231
+ let real;
232
+ try {
233
+ real = await realpath(path);
234
+ }
235
+ catch {
236
+ throw pinMismatch("executable is missing");
237
+ }
238
+ if (real !== path)
239
+ throw pinMismatch("executable path is not canonical");
240
+ const noFollow = fsConstants.O_NOFOLLOW;
241
+ if (noFollow === undefined)
242
+ throw pinMismatch("O_NOFOLLOW is unavailable");
243
+ let handle;
244
+ try {
245
+ handle = await open(path, fsConstants.O_RDONLY | noFollow);
246
+ }
247
+ catch {
248
+ throw pinMismatch("executable could not be opened without following links");
249
+ }
250
+ let actual;
251
+ try {
252
+ if (!(await handle.stat()).isFile()) {
253
+ throw pinMismatch("executable is not a regular file");
254
+ }
255
+ actual = await digestOfHandle(handle);
256
+ }
257
+ finally {
258
+ await handle.close().catch(() => undefined);
259
+ }
260
+ if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) {
261
+ throw pinMismatch("executable digest does not match the pin");
262
+ }
263
+ }
264
+ /** SHA-256 streamed off an already-open descriptor by explicit position. */
265
+ async function digestOfHandle(handle) {
266
+ const hash = createHash("sha256");
267
+ const buffer = Buffer.allocUnsafe(HASH_CHUNK_BYTES);
268
+ let position = 0;
269
+ for (;;) {
270
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
271
+ if (bytesRead === 0)
272
+ break;
273
+ position += bytesRead;
274
+ if (position > MAX_EXECUTABLE_BYTES) {
275
+ throw pinMismatch("executable exceeds the size limit");
276
+ }
277
+ hash.update(buffer.subarray(0, bytesRead));
278
+ }
279
+ return hash.digest();
280
+ }
281
+ /**
282
+ * The argv (after the executable) for one authorize call. `agentId` and
283
+ * `workspaceId` are not sent: kei-proxy (>= 0.1.11) resolves the agent from
284
+ * the runtime installation and workspace scope from the runtime token.
285
+ */
286
+ export function keiProxyAuthorizeArgv(request) {
287
+ const argv = [
288
+ "authorize",
289
+ "--user",
290
+ request.userId,
291
+ "--tool",
292
+ request.tool,
293
+ "--action",
294
+ request.action,
295
+ "--resource",
296
+ request.resource,
297
+ ];
298
+ const optional = [
299
+ ["--span-id", request.spanId],
300
+ ["--invoking-subject", request.invokingSubject],
301
+ ["--parent-span", request.parentSpan],
302
+ [
303
+ "--delegation-depth",
304
+ request.delegationDepth > 0 ? String(request.delegationDepth) : "",
305
+ ],
306
+ ["--agent-version", request.agentVersion],
307
+ ["--framework", request.framework],
308
+ ["--tool-args-digest", request.toolArgsDigest],
309
+ ["--resources", request.resources.join(",")],
310
+ ];
311
+ for (const [flag, value] of optional) {
312
+ if (value)
313
+ argv.push(flag, value);
314
+ }
315
+ return argv;
316
+ }
317
+ /** Validate one authorize result against the exit-code contract. */
318
+ export function parseKeiProxyAuthorizeOutput(stdout, exitCode) {
319
+ if (exitCode !== 0 && exitCode !== 1) {
320
+ throw new KeiProxyAuthorizeError("proxy_error", `kei-proxy exited ${exitCode}`);
321
+ }
322
+ const text = stdout.trim();
323
+ if (!text) {
324
+ if (exitCode !== 0) {
325
+ throw new KeiProxyAuthorizeError("proxy_error", `kei-proxy exited ${exitCode}`);
326
+ }
327
+ throw new KeiProxyAuthorizeError("empty_response", "kei-proxy printed nothing");
328
+ }
329
+ let parsed;
330
+ try {
331
+ parsed = JSON.parse(text);
332
+ }
333
+ catch {
334
+ throw new KeiProxyAuthorizeError("malformed_response", "stdout is not JSON");
335
+ }
336
+ if (!isPlainObject(parsed)) {
337
+ throw new KeiProxyAuthorizeError("malformed_response", "stdout is not a JSON object");
338
+ }
339
+ const decision = parsed.decision;
340
+ if (exitCode !== 0 &&
341
+ typeof decision === "string" &&
342
+ KEI_PROXY_AFFIRMATIVE_DECISIONS.has(decision.trim().toLowerCase())) {
343
+ throw new KeiProxyAuthorizeError("exit_mismatch", `${decision} with exit ${exitCode}`);
344
+ }
345
+ return parsed;
346
+ }
347
+ function isPlainObject(value) {
348
+ return typeof value === "object" && value !== null && !Array.isArray(value);
349
+ }
@@ -0,0 +1,74 @@
1
+ import { type CallerContext, type Decision } from "../middleware/types.js";
2
+ /** kei-proxy's affirmative decisions. Everything else denies. */
3
+ export declare const KEI_PROXY_AFFIRMATIVE_DECISIONS: ReadonlySet<string>;
4
+ /** Every reason reads `kei-proxy <class>` or `kei-proxy <class>: <detail>`. */
5
+ export declare const KEI_PROXY_REASON_CLASSES: readonly ["allow", "deny", "enrollment_required", "unknown_decision", "no_decision", "malformed_response", "empty_response", "proxy_error", "exit_mismatch", "proxy_unavailable", "proxy_timeout", "missing_token", "pin_mismatch"];
6
+ export type KeiProxyReasonClass = (typeof KEI_PROXY_REASON_CLASSES)[number];
7
+ /**
8
+ * An authorize call that produced no usable answer. The message is built by
9
+ * this library and never contains proxy stdout or stderr.
10
+ */
11
+ export declare class KeiProxyAuthorizeError extends Error {
12
+ readonly reasonClass: KeiProxyReasonClass;
13
+ readonly detail: string;
14
+ constructor(reasonClass: KeiProxyReasonClass, detail: string);
15
+ }
16
+ /** The canonical audit context sent with one authorize call. */
17
+ export interface KeiProxyAuthorizeRequest {
18
+ userId: string;
19
+ tool: string;
20
+ action: string;
21
+ resource: string;
22
+ spanId: string;
23
+ invokingSubject: string;
24
+ parentSpan: string;
25
+ delegationDepth: number;
26
+ agentId: string;
27
+ agentVersion: string;
28
+ framework: string;
29
+ toolArgsDigest: string;
30
+ resources: string[];
31
+ /** Accepted for parity; kei-proxy derives workspace scope from the runtime token. */
32
+ workspaceId: string;
33
+ }
34
+ /**
35
+ * The single call the evaluator needs. Return (or resolve to) the parsed
36
+ * authorize object; throw or reject with `KeiProxyAuthorizeError` (or
37
+ * anything) to deny.
38
+ */
39
+ export interface KeiProxyAuthorizationClient {
40
+ authorize(request: KeiProxyAuthorizeRequest): unknown;
41
+ }
42
+ /**
43
+ * Derive `type:kind:id` resource identifiers from a tool call's arguments.
44
+ * Only resources named in the arguments are knowable before the call.
45
+ */
46
+ export declare function keiProxyResourcesTouched(toolName: string, args: Record<string, unknown>): string[];
47
+ /**
48
+ * SHA-256 of the args as compact, sorted-key, ASCII-escaped JSON: byte-for-byte
49
+ * Python's `json.dumps(args, sort_keys=True, separators=(",", ":"))` for
50
+ * strings, integers, booleans, null, arrays and objects.
51
+ */
52
+ export declare function keiProxyToolArgsDigest(args: Record<string, unknown>): string;
53
+ export type KeiProxyLogger = (level: "debug" | "warn", message: string) => void;
54
+ export interface KeiProxyEvaluatorOptions {
55
+ /** Action verb sent when a tool call names none in `args.action`. Default `execute`. */
56
+ defaultAction?: string;
57
+ /** Receives class-level diagnostics only. Default: `console.warn` for warnings. */
58
+ logger?: KeiProxyLogger;
59
+ }
60
+ /**
61
+ * A policy evaluator over kei-proxy. Translates the proxy's decision into an
62
+ * agentware Decision and fails closed on every path that is not an explicit
63
+ * affirmative. See `docs/kei-proxy-evaluator-parity.md` for the table.
64
+ */
65
+ export declare class KeiProxyEvaluator {
66
+ static readonly RULE = "kei-proxy";
67
+ private readonly client;
68
+ private readonly defaultAction;
69
+ private readonly logger;
70
+ constructor(client: KeiProxyAuthorizationClient, options?: KeiProxyEvaluatorOptions);
71
+ evaluate(toolName: string, args: Record<string, unknown>, caller: CallerContext): Promise<Decision>;
72
+ private decision;
73
+ }
74
+ //# sourceMappingURL=evaluator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/kei/evaluator.ts"],"names":[],"mappings":"AAiBA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,QAAQ,EACd,MAAM,wBAAwB,CAAC;AAEhC,iEAAiE;AACjE,eAAO,MAAM,+BAA+B,EAAE,WAAW,CAAC,MAAM,CAG9D,CAAC;AAEH,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,qOAc3B,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,WAAW,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM;CAM7D;AAED,gEAAgE;AAChE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC;CACvD;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,EAAE,CAoBV;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE5E;AAwBD,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAEhF,MAAM,WAAW,wBAAwB;IACvC,wFAAwF;IACxF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAMD;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,QAAQ,CAAC,IAAI,eAAe;IAEnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;gBAGtC,MAAM,EAAE,2BAA2B,EACnC,OAAO,GAAE,wBAA6B;IAOlC,QAAQ,CACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,QAAQ,CAAC;IA2FpB,OAAO,CAAC,QAAQ;CAejB"}
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Policy evaluation backed by `kei-proxy authorize`.
3
+ *
4
+ * TypeScript port of `pedro_agentware.kei.evaluator`. All three SDK languages
5
+ * are held to the parity table in `fixtures/kei/authorize-cases.v1.json`
6
+ * (see `docs/kei-proxy-evaluator-parity.md`): the same proxy output yields the
7
+ * same Decision, reason class and enrollment everywhere.
8
+ *
9
+ * Fail closed: only an explicit `allow`/`permit` with exit 0 allows. This
10
+ * module never spawns the proxy: `KeiProxyAuthorizeClient`
11
+ * (`authorizeClient.ts`) does, behind the `KeiProxyAuthorizationClient` seam.
12
+ *
13
+ * `evaluate` is asynchronous so a harness's event loop keeps running while
14
+ * kei-proxy answers. It therefore does not implement the synchronous
15
+ * `PolicyEvaluator`; await it before running the tool.
16
+ */
17
+ import { createHash } from "node:crypto";
18
+ import { Action, } from "../middleware/types.js";
19
+ /** kei-proxy's affirmative decisions. Everything else denies. */
20
+ export const KEI_PROXY_AFFIRMATIVE_DECISIONS = new Set([
21
+ "permit",
22
+ "allow",
23
+ ]);
24
+ /** Every reason reads `kei-proxy <class>` or `kei-proxy <class>: <detail>`. */
25
+ export const KEI_PROXY_REASON_CLASSES = [
26
+ "allow",
27
+ "deny",
28
+ "enrollment_required",
29
+ "unknown_decision",
30
+ "no_decision",
31
+ "malformed_response",
32
+ "empty_response",
33
+ "proxy_error",
34
+ "exit_mismatch",
35
+ "proxy_unavailable",
36
+ "proxy_timeout",
37
+ "missing_token",
38
+ "pin_mismatch",
39
+ ];
40
+ /**
41
+ * An authorize call that produced no usable answer. The message is built by
42
+ * this library and never contains proxy stdout or stderr.
43
+ */
44
+ export class KeiProxyAuthorizeError extends Error {
45
+ reasonClass;
46
+ detail;
47
+ constructor(reasonClass, detail) {
48
+ super(detail);
49
+ this.name = "KeiProxyAuthorizeError";
50
+ this.reasonClass = reasonClass;
51
+ this.detail = detail;
52
+ }
53
+ }
54
+ /**
55
+ * Derive `type:kind:id` resource identifiers from a tool call's arguments.
56
+ * Only resources named in the arguments are knowable before the call.
57
+ */
58
+ export function keiProxyResourcesTouched(toolName, args) {
59
+ const resourceType = toolName.split(".", 1)[0].split("_", 1)[0].toLowerCase();
60
+ if (!resourceType)
61
+ return [];
62
+ const touched = [];
63
+ const add = (kind, identifier) => {
64
+ if (identifier === undefined || identifier === null)
65
+ return;
66
+ const value = String(identifier).trim();
67
+ if (!value)
68
+ return;
69
+ const entry = `${resourceType}:${kind}:${value}`;
70
+ if (!touched.includes(entry))
71
+ touched.push(entry);
72
+ };
73
+ const owner = args.owner;
74
+ const repo = args.repo || args.repository;
75
+ if (owner && repo)
76
+ add("repo", `${String(owner)}/${String(repo)}`);
77
+ else if (repo)
78
+ add("repo", repo);
79
+ add("issue", args.issue_number);
80
+ add("pull_request", args.pull_number || args.pr_number);
81
+ add("branch", args.branch || args.ref);
82
+ add("file", args.path || args.file_path);
83
+ return touched;
84
+ }
85
+ /**
86
+ * SHA-256 of the args as compact, sorted-key, ASCII-escaped JSON: byte-for-byte
87
+ * Python's `json.dumps(args, sort_keys=True, separators=(",", ":"))` for
88
+ * strings, integers, booleans, null, arrays and objects.
89
+ */
90
+ export function keiProxyToolArgsDigest(args) {
91
+ return createHash("sha256").update(canonicalJson(args), "utf8").digest("hex");
92
+ }
93
+ function canonicalJson(value) {
94
+ if (Array.isArray(value))
95
+ return `[${value.map(canonicalJson).join(",")}]`;
96
+ if (isPlainObject(value)) {
97
+ const keys = Object.keys(value).sort();
98
+ return `{${keys.map((k) => `${asciiJson(k)}:${canonicalJson(value[k])}`).join(",")}}`;
99
+ }
100
+ if (typeof value === "string")
101
+ return asciiJson(value);
102
+ if (value === undefined)
103
+ return "null";
104
+ return JSON.stringify(value) ?? "null";
105
+ }
106
+ function asciiJson(text) {
107
+ return JSON.stringify(text).replace(/[\u0080-\uffff]/g, (ch) => `\\u${ch.charCodeAt(0).toString(16).padStart(4, "0")}`);
108
+ }
109
+ function isPlainObject(value) {
110
+ return typeof value === "object" && value !== null && !Array.isArray(value);
111
+ }
112
+ const defaultLogger = (level, message) => {
113
+ if (level === "warn")
114
+ console.warn(message);
115
+ };
116
+ /**
117
+ * A policy evaluator over kei-proxy. Translates the proxy's decision into an
118
+ * agentware Decision and fails closed on every path that is not an explicit
119
+ * affirmative. See `docs/kei-proxy-evaluator-parity.md` for the table.
120
+ */
121
+ export class KeiProxyEvaluator {
122
+ static RULE = "kei-proxy";
123
+ client;
124
+ defaultAction;
125
+ logger;
126
+ constructor(client, options = {}) {
127
+ this.client = client;
128
+ this.defaultAction = options.defaultAction ?? "execute";
129
+ this.logger = options.logger ?? defaultLogger;
130
+ }
131
+ async evaluate(toolName, args, caller) {
132
+ const resources = keiProxyResourcesTouched(toolName, args);
133
+ const userId = caller.invoking_subject || caller.user_id || "";
134
+ const meta = caller.metadata ?? {};
135
+ const request = {
136
+ userId,
137
+ tool: toolName,
138
+ action: String(args.action || this.defaultAction),
139
+ resource: resources[0] ?? toolName,
140
+ spanId: meta.span_id ?? "",
141
+ invokingSubject: userId,
142
+ parentSpan: caller.parent_span ?? "",
143
+ delegationDepth: caller.delegation_depth ?? 0,
144
+ agentId: meta.agent_id ?? "",
145
+ agentVersion: meta.agent_version ?? "",
146
+ framework: meta.framework || caller.source || "",
147
+ toolArgsDigest: keiProxyToolArgsDigest(args),
148
+ resources,
149
+ workspaceId: meta.workspace_id ?? "",
150
+ };
151
+ let raw;
152
+ try {
153
+ raw = await this.client.authorize(request);
154
+ }
155
+ catch (err) {
156
+ if (err instanceof KeiProxyAuthorizeError) {
157
+ this.logger("warn", `kei-proxy authorize for ${toolName} denied: ${err.reasonClass}`);
158
+ return this.decision(Action.DENY, reason(err.reasonClass, err.detail));
159
+ }
160
+ const message = err instanceof Error ? err.message : String(err);
161
+ this.logger("warn", `kei-proxy authorize failed for ${toolName}, denying: ${message}`);
162
+ return this.decision(Action.DENY, reason("proxy_error", `authorize failed: ${message}`));
163
+ }
164
+ const response = isPlainObject(raw) ? raw : {};
165
+ const rawDecision = response.decision;
166
+ const decision = rawDecision === undefined || rawDecision === null
167
+ ? ""
168
+ : String(rawDecision).trim().toLowerCase();
169
+ const proxyReason = optionalString(response.reason);
170
+ const policyId = optionalString(response.policy_id) ?? optionalString(response.policy);
171
+ if (KEI_PROXY_AFFIRMATIVE_DECISIONS.has(decision)) {
172
+ return this.decision(Action.ALLOW, reason("allow", proxyReason), policyId);
173
+ }
174
+ let reasonClass;
175
+ let detail;
176
+ let enrollment;
177
+ const carried = isPlainObject(response.enrollment)
178
+ ? response.enrollment
179
+ : undefined;
180
+ if (!decision) {
181
+ reasonClass = "no_decision";
182
+ }
183
+ else if (decision === "deny") {
184
+ reasonClass = "deny";
185
+ detail = proxyReason;
186
+ enrollment = carried;
187
+ }
188
+ else if (decision === "enrollment_required") {
189
+ reasonClass = "enrollment_required";
190
+ detail = proxyReason ?? "enrollment is required before this call";
191
+ enrollment = carried;
192
+ }
193
+ else {
194
+ reasonClass = "unknown_decision";
195
+ detail = `'${decision}'` + (proxyReason ? ` (${proxyReason})` : "");
196
+ }
197
+ this.logger("debug", `kei-proxy ${reasonClass} for ${toolName}`);
198
+ return this.decision(Action.DENY, reason(reasonClass, detail), policyId, enrollment);
199
+ }
200
+ decision(action, reasonText, policyId, enrollment) {
201
+ const out = {
202
+ action,
203
+ rule: policyId ?? KeiProxyEvaluator.RULE,
204
+ reason: reasonText,
205
+ timestamp: new Date(),
206
+ };
207
+ if (enrollment !== undefined)
208
+ out.enrollment = enrollment;
209
+ return out;
210
+ }
211
+ }
212
+ function reason(reasonClass, detail) {
213
+ return detail
214
+ ? `kei-proxy ${reasonClass}: ${detail}`
215
+ : `kei-proxy ${reasonClass}`;
216
+ }
217
+ function optionalString(value) {
218
+ if (value === undefined || value === null)
219
+ return undefined;
220
+ const text = String(value);
221
+ return text ? text : undefined;
222
+ }
@@ -1,3 +1,5 @@
1
1
  export * from "./runtimeLink.js";
2
2
  export * from "./runtimeLinkRuntime.js";
3
+ export * from "./evaluator.js";
4
+ export * from "./authorizeClient.js";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/kei/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/kei/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
package/dist/kei/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./runtimeLink.js";
2
2
  export * from "./runtimeLinkRuntime.js";
3
+ export * from "./evaluator.js";
4
+ export * from "./authorizeClient.js";
@@ -29,12 +29,24 @@ export interface CallerContext {
29
29
  source?: string;
30
30
  trusted: boolean;
31
31
  metadata?: Record<string, string>;
32
+ /** The human who originated the request; carried unchanged across delegation hops. */
33
+ invoking_subject?: string;
34
+ /** Span of the delegating caller, when this call was made by a subagent. */
35
+ parent_span?: string;
36
+ /** 0 for a direct call; incremented at each delegation hop. */
37
+ delegation_depth?: number;
32
38
  }
33
39
  export interface Decision {
34
40
  action: Action;
35
41
  rule: string;
36
42
  reason: string;
37
43
  redacted_args?: Record<string, unknown>;
44
+ /**
45
+ * Opaque enrollment object from kei-proxy on a DENY (provider,
46
+ * provider_user_id, org_id, workspace_id, and optionally url/expires_at).
47
+ * The url is a one-time claim link: surface it to the user, never log it.
48
+ */
49
+ enrollment?: Record<string, unknown>;
38
50
  timestamp: Date;
39
51
  }
40
52
  export declare function createAllowDecision(reason?: string): Decision;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/middleware/types.ts"],"names":[],"mappings":"AAAA,oBAAY,WAAW;IACrB,aAAa,kBAAkB;IAC/B,UAAU,eAAe;IACzB,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,SAAS,cAAc;IACvB,aAAa,kBAAkB;IAC/B,UAAU,eAAe;IACzB,kBAAkB,uBAAuB;IACzC,WAAW,gBAAgB;IAC3B,eAAe,oBAAoB;IACnC,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,oBAAY,MAAM;IAChB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,MAAM,WAAW;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,SAAY,GAAG,QAAQ,CAOhE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAO3D"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/middleware/types.ts"],"names":[],"mappings":"AAAA,oBAAY,WAAW;IACrB,aAAa,kBAAkB;IAC/B,UAAU,eAAe;IACzB,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,SAAS,cAAc;IACvB,aAAa,kBAAkB;IAC/B,UAAU,eAAe;IACzB,kBAAkB,uBAAuB;IACzC,WAAW,gBAAgB;IAC3B,eAAe,oBAAoB;IACnC,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,oBAAY,MAAM;IAChB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,MAAM,WAAW;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,SAAY,GAAG,QAAQ,CAOhE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAO3D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haikeilabs/agentware",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Agent middleware and tooling for LLM tool calling",
5
5
  "repository": {
6
6
  "type": "git",