@oharness/engine 0.1.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.
Files changed (256) hide show
  1. package/dist/agent/agent.d.ts +196 -0
  2. package/dist/agent/agent.js +758 -0
  3. package/dist/agent/agent.js.map +1 -0
  4. package/dist/agent/compaction.d.ts +68 -0
  5. package/dist/agent/compaction.js +179 -0
  6. package/dist/agent/compaction.js.map +1 -0
  7. package/dist/agent/hooks.d.ts +77 -0
  8. package/dist/agent/hooks.js +152 -0
  9. package/dist/agent/hooks.js.map +1 -0
  10. package/dist/agent/memory.d.ts +45 -0
  11. package/dist/agent/memory.js +147 -0
  12. package/dist/agent/memory.js.map +1 -0
  13. package/dist/agent/overflow.d.ts +35 -0
  14. package/dist/agent/overflow.js +69 -0
  15. package/dist/agent/overflow.js.map +1 -0
  16. package/dist/agent/permissions.d.ts +181 -0
  17. package/dist/agent/permissions.js +289 -0
  18. package/dist/agent/permissions.js.map +1 -0
  19. package/dist/agent/prompt.d.ts +25 -0
  20. package/dist/agent/prompt.js +63 -0
  21. package/dist/agent/prompt.js.map +1 -0
  22. package/dist/agent/session.d.ts +108 -0
  23. package/dist/agent/session.js +231 -0
  24. package/dist/agent/session.js.map +1 -0
  25. package/dist/agent/settings.d.ts +37 -0
  26. package/dist/agent/settings.js +113 -0
  27. package/dist/agent/settings.js.map +1 -0
  28. package/dist/agent/skills.d.ts +58 -0
  29. package/dist/agent/skills.js +225 -0
  30. package/dist/agent/skills.js.map +1 -0
  31. package/dist/agent/store.d.ts +308 -0
  32. package/dist/agent/store.js +596 -0
  33. package/dist/agent/store.js.map +1 -0
  34. package/dist/agent/subagent.d.ts +44 -0
  35. package/dist/agent/subagent.js +138 -0
  36. package/dist/agent/subagent.js.map +1 -0
  37. package/dist/agent/titles.d.ts +8 -0
  38. package/dist/agent/titles.js +23 -0
  39. package/dist/agent/titles.js.map +1 -0
  40. package/dist/agent/workspace.d.ts +60 -0
  41. package/dist/agent/workspace.js +156 -0
  42. package/dist/agent/workspace.js.map +1 -0
  43. package/dist/artifacts/publish.d.ts +94 -0
  44. package/dist/artifacts/publish.js +104 -0
  45. package/dist/artifacts/publish.js.map +1 -0
  46. package/dist/artifacts/store.d.ts +324 -0
  47. package/dist/artifacts/store.js +500 -0
  48. package/dist/artifacts/store.js.map +1 -0
  49. package/dist/auth/index.d.ts +13 -0
  50. package/dist/auth/index.js +10 -0
  51. package/dist/auth/index.js.map +1 -0
  52. package/dist/auth/manager.d.ts +83 -0
  53. package/dist/auth/manager.js +266 -0
  54. package/dist/auth/manager.js.map +1 -0
  55. package/dist/auth/oauth.d.ts +83 -0
  56. package/dist/auth/oauth.js +327 -0
  57. package/dist/auth/oauth.js.map +1 -0
  58. package/dist/auth/providers/account.d.ts +59 -0
  59. package/dist/auth/providers/account.js +282 -0
  60. package/dist/auth/providers/account.js.map +1 -0
  61. package/dist/auth/providers/anthropic.d.ts +24 -0
  62. package/dist/auth/providers/anthropic.js +209 -0
  63. package/dist/auth/providers/anthropic.js.map +1 -0
  64. package/dist/auth/providers/generic.d.ts +57 -0
  65. package/dist/auth/providers/generic.js +155 -0
  66. package/dist/auth/providers/generic.js.map +1 -0
  67. package/dist/auth/providers/official.d.ts +3 -0
  68. package/dist/auth/providers/official.js +90 -0
  69. package/dist/auth/providers/official.js.map +1 -0
  70. package/dist/auth/providers/ottoport.d.ts +3 -0
  71. package/dist/auth/providers/ottoport.js +60 -0
  72. package/dist/auth/providers/ottoport.js.map +1 -0
  73. package/dist/auth/store.d.ts +22 -0
  74. package/dist/auth/store.js +138 -0
  75. package/dist/auth/store.js.map +1 -0
  76. package/dist/auth/types.d.ts +167 -0
  77. package/dist/auth/types.js +16 -0
  78. package/dist/auth/types.js.map +1 -0
  79. package/dist/config.d.ts +197 -0
  80. package/dist/config.js +195 -0
  81. package/dist/config.js.map +1 -0
  82. package/dist/core/errors.d.ts +46 -0
  83. package/dist/core/errors.js +83 -0
  84. package/dist/core/errors.js.map +1 -0
  85. package/dist/core/tokens.d.ts +12 -0
  86. package/dist/core/tokens.js +56 -0
  87. package/dist/core/tokens.js.map +1 -0
  88. package/dist/core/types.d.ts +91 -0
  89. package/dist/core/types.js +49 -0
  90. package/dist/core/types.js.map +1 -0
  91. package/dist/harness.d.ts +302 -0
  92. package/dist/harness.js +695 -0
  93. package/dist/harness.js.map +1 -0
  94. package/dist/index.d.ts +32 -0
  95. package/dist/index.js +21 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/mcp/client.d.ts +55 -0
  98. package/dist/mcp/client.js +199 -0
  99. package/dist/mcp/client.js.map +1 -0
  100. package/dist/mcp/index.d.ts +13 -0
  101. package/dist/mcp/index.js +8 -0
  102. package/dist/mcp/index.js.map +1 -0
  103. package/dist/mcp/manager.d.ts +77 -0
  104. package/dist/mcp/manager.js +325 -0
  105. package/dist/mcp/manager.js.map +1 -0
  106. package/dist/mcp/oauth.d.ts +63 -0
  107. package/dist/mcp/oauth.js +302 -0
  108. package/dist/mcp/oauth.js.map +1 -0
  109. package/dist/mcp/protocol.d.ts +130 -0
  110. package/dist/mcp/protocol.js +14 -0
  111. package/dist/mcp/protocol.js.map +1 -0
  112. package/dist/mcp/registry.d.ts +52 -0
  113. package/dist/mcp/registry.js +100 -0
  114. package/dist/mcp/registry.js.map +1 -0
  115. package/dist/mcp/registry.json +293 -0
  116. package/dist/mcp/secrets.d.ts +20 -0
  117. package/dist/mcp/secrets.js +75 -0
  118. package/dist/mcp/secrets.js.map +1 -0
  119. package/dist/mcp/transport.d.ts +51 -0
  120. package/dist/mcp/transport.js +158 -0
  121. package/dist/mcp/transport.js.map +1 -0
  122. package/dist/paths.d.ts +37 -0
  123. package/dist/paths.js +63 -0
  124. package/dist/paths.js.map +1 -0
  125. package/dist/plugins.d.ts +55 -0
  126. package/dist/plugins.js +201 -0
  127. package/dist/plugins.js.map +1 -0
  128. package/dist/providers/builtin.d.ts +23 -0
  129. package/dist/providers/builtin.js +141 -0
  130. package/dist/providers/builtin.js.map +1 -0
  131. package/dist/providers/catalog.d.ts +129 -0
  132. package/dist/providers/catalog.js +254 -0
  133. package/dist/providers/catalog.js.map +1 -0
  134. package/dist/providers/catalog.json +472 -0
  135. package/dist/providers/discovery.d.ts +16 -0
  136. package/dist/providers/discovery.js +161 -0
  137. package/dist/providers/discovery.js.map +1 -0
  138. package/dist/providers/http.d.ts +24 -0
  139. package/dist/providers/http.js +139 -0
  140. package/dist/providers/http.js.map +1 -0
  141. package/dist/providers/index.d.ts +10 -0
  142. package/dist/providers/index.js +8 -0
  143. package/dist/providers/index.js.map +1 -0
  144. package/dist/providers/piai.d.ts +124 -0
  145. package/dist/providers/piai.js +551 -0
  146. package/dist/providers/piai.js.map +1 -0
  147. package/dist/providers/registry.d.ts +65 -0
  148. package/dist/providers/registry.js +195 -0
  149. package/dist/providers/registry.js.map +1 -0
  150. package/dist/providers/resilient.d.ts +46 -0
  151. package/dist/providers/resilient.js +117 -0
  152. package/dist/providers/resilient.js.map +1 -0
  153. package/dist/providers/types.d.ts +182 -0
  154. package/dist/providers/types.js +50 -0
  155. package/dist/providers/types.js.map +1 -0
  156. package/dist/schedule/cron.d.ts +35 -0
  157. package/dist/schedule/cron.js +125 -0
  158. package/dist/schedule/cron.js.map +1 -0
  159. package/dist/schedule/scheduler.d.ts +87 -0
  160. package/dist/schedule/scheduler.js +237 -0
  161. package/dist/schedule/scheduler.js.map +1 -0
  162. package/dist/storage/kv.d.ts +56 -0
  163. package/dist/storage/kv.js +173 -0
  164. package/dist/storage/kv.js.map +1 -0
  165. package/dist/storage/migrate.d.ts +11 -0
  166. package/dist/storage/migrate.js +93 -0
  167. package/dist/storage/migrate.js.map +1 -0
  168. package/dist/storage/project.d.ts +9 -0
  169. package/dist/storage/project.js +57 -0
  170. package/dist/storage/project.js.map +1 -0
  171. package/dist/storage/records.d.ts +32 -0
  172. package/dist/storage/records.js +48 -0
  173. package/dist/storage/records.js.map +1 -0
  174. package/dist/sync/client.d.ts +79 -0
  175. package/dist/sync/client.js +118 -0
  176. package/dist/sync/client.js.map +1 -0
  177. package/dist/sync/mirror.d.ts +85 -0
  178. package/dist/sync/mirror.js +389 -0
  179. package/dist/sync/mirror.js.map +1 -0
  180. package/dist/sync/protocol.d.ts +67 -0
  181. package/dist/sync/protocol.js +16 -0
  182. package/dist/sync/protocol.js.map +1 -0
  183. package/dist/sync/settings.d.ts +56 -0
  184. package/dist/sync/settings.js +60 -0
  185. package/dist/sync/settings.js.map +1 -0
  186. package/dist/tools/builtin/artifact.d.ts +16 -0
  187. package/dist/tools/builtin/artifact.js +134 -0
  188. package/dist/tools/builtin/artifact.js.map +1 -0
  189. package/dist/tools/builtin/bash.d.ts +3 -0
  190. package/dist/tools/builtin/bash.js +142 -0
  191. package/dist/tools/builtin/bash.js.map +1 -0
  192. package/dist/tools/builtin/fs.d.ts +19 -0
  193. package/dist/tools/builtin/fs.js +280 -0
  194. package/dist/tools/builtin/fs.js.map +1 -0
  195. package/dist/tools/builtin/index.d.ts +22 -0
  196. package/dist/tools/builtin/index.js +42 -0
  197. package/dist/tools/builtin/index.js.map +1 -0
  198. package/dist/tools/builtin/mcp-resource.d.ts +3 -0
  199. package/dist/tools/builtin/mcp-resource.js +220 -0
  200. package/dist/tools/builtin/mcp-resource.js.map +1 -0
  201. package/dist/tools/builtin/memory.d.ts +10 -0
  202. package/dist/tools/builtin/memory.js +76 -0
  203. package/dist/tools/builtin/memory.js.map +1 -0
  204. package/dist/tools/builtin/recall.d.ts +13 -0
  205. package/dist/tools/builtin/recall.js +163 -0
  206. package/dist/tools/builtin/recall.js.map +1 -0
  207. package/dist/tools/builtin/search.d.ts +3 -0
  208. package/dist/tools/builtin/search.js +240 -0
  209. package/dist/tools/builtin/search.js.map +1 -0
  210. package/dist/tools/builtin/skill.d.ts +10 -0
  211. package/dist/tools/builtin/skill.js +46 -0
  212. package/dist/tools/builtin/skill.js.map +1 -0
  213. package/dist/tools/builtin/task.d.ts +9 -0
  214. package/dist/tools/builtin/task.js +68 -0
  215. package/dist/tools/builtin/task.js.map +1 -0
  216. package/dist/tools/builtin/todo.d.ts +20 -0
  217. package/dist/tools/builtin/todo.js +63 -0
  218. package/dist/tools/builtin/todo.js.map +1 -0
  219. package/dist/tools/define.d.ts +19 -0
  220. package/dist/tools/define.js +39 -0
  221. package/dist/tools/define.js.map +1 -0
  222. package/dist/tools/index.d.ts +7 -0
  223. package/dist/tools/index.js +5 -0
  224. package/dist/tools/index.js.map +1 -0
  225. package/dist/tools/registry.d.ts +39 -0
  226. package/dist/tools/registry.js +101 -0
  227. package/dist/tools/registry.js.map +1 -0
  228. package/dist/tools/types.d.ts +112 -0
  229. package/dist/tools/types.js +22 -0
  230. package/dist/tools/types.js.map +1 -0
  231. package/dist/util/id.d.ts +9 -0
  232. package/dist/util/id.js +19 -0
  233. package/dist/util/id.js.map +1 -0
  234. package/dist/util/json.d.ts +25 -0
  235. package/dist/util/json.js +100 -0
  236. package/dist/util/json.js.map +1 -0
  237. package/dist/util/jwt.d.ts +74 -0
  238. package/dist/util/jwt.js +177 -0
  239. package/dist/util/jwt.js.map +1 -0
  240. package/dist/util/logger.d.ts +13 -0
  241. package/dist/util/logger.js +59 -0
  242. package/dist/util/logger.js.map +1 -0
  243. package/dist/util/queue.d.ts +26 -0
  244. package/dist/util/queue.js +106 -0
  245. package/dist/util/queue.js.map +1 -0
  246. package/dist/util/schema.d.ts +17 -0
  247. package/dist/util/schema.js +98 -0
  248. package/dist/util/schema.js.map +1 -0
  249. package/dist/util/shell.d.ts +20 -0
  250. package/dist/util/shell.js +24 -0
  251. package/dist/util/shell.js.map +1 -0
  252. package/official.json +4 -0
  253. package/package.json +38 -0
  254. package/skills/artifact-authoring/SKILL.md +81 -0
  255. package/skills/canvas/SKILL.md +85 -0
  256. package/skills/skill-authoring/SKILL.md +85 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Tolerant JSON helpers for streamed tool arguments.
3
+ *
4
+ * Providers emit tool inputs as a stream of JSON fragments. To render a
5
+ * live preview ("Bash(npm run bui…") we need to parse a prefix of a JSON
6
+ * document, so `parsePartialJson` closes any open strings/arrays/objects
7
+ * before handing the text to JSON.parse.
8
+ */
9
+ export function parseJson(raw) {
10
+ try {
11
+ return { ok: true, value: JSON.parse(raw) };
12
+ }
13
+ catch (err) {
14
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
15
+ }
16
+ }
17
+ export function parsePartialJson(raw) {
18
+ const trimmed = raw.trim();
19
+ if (trimmed === "")
20
+ return undefined;
21
+ try {
22
+ return JSON.parse(trimmed);
23
+ }
24
+ catch {
25
+ // fall through to repair
26
+ }
27
+ const repaired = closeOpenStructures(trimmed);
28
+ if (repaired === undefined)
29
+ return undefined;
30
+ try {
31
+ return JSON.parse(repaired);
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ }
37
+ function closeOpenStructures(input) {
38
+ const stack = [];
39
+ let inString = false;
40
+ let escaped = false;
41
+ for (let i = 0; i < input.length; i++) {
42
+ const ch = input[i];
43
+ if (inString) {
44
+ if (escaped)
45
+ escaped = false;
46
+ else if (ch === "\\")
47
+ escaped = true;
48
+ else if (ch === '"')
49
+ inString = false;
50
+ continue;
51
+ }
52
+ if (ch === '"')
53
+ inString = true;
54
+ else if (ch === "{")
55
+ stack.push("}");
56
+ else if (ch === "[")
57
+ stack.push("]");
58
+ else if (ch === "}" || ch === "]")
59
+ stack.pop();
60
+ }
61
+ let out = input;
62
+ // A trailing escape would break the string we are about to close.
63
+ if (escaped)
64
+ out = out.slice(0, -1);
65
+ if (inString)
66
+ out += '"';
67
+ // Drop dangling separators like `{"a":1,` or `{"a":`
68
+ out = out.replace(/([,:])\s*$/, (_m, sep) => (sep === ":" ? ":null" : ""));
69
+ for (let i = stack.length - 1; i >= 0; i--)
70
+ out += stack[i];
71
+ return out;
72
+ }
73
+ /**
74
+ * Decode accumulated tool-argument JSON. Models occasionally emit a truncated
75
+ * or slightly malformed object; falling back to the partial parser keeps the
76
+ * turn alive, and an empty object lets the tool's own validation produce a
77
+ * useful error message instead of crashing the loop.
78
+ */
79
+ export function decodeToolInput(raw) {
80
+ if (raw.trim() === "")
81
+ return {};
82
+ const direct = parseJson(raw);
83
+ if (direct.ok)
84
+ return direct.value;
85
+ const partial = parsePartialJson(raw);
86
+ return partial === undefined ? {} : partial;
87
+ }
88
+ /** Truncate deeply/hugely nested values before logging or previewing. */
89
+ export function previewJson(value, maxLength = 120) {
90
+ let s;
91
+ try {
92
+ s = typeof value === "string" ? value : JSON.stringify(value) ?? String(value);
93
+ }
94
+ catch {
95
+ s = String(value);
96
+ }
97
+ s = s.replaceAll(/\s+/g, " ");
98
+ return s.length > maxLength ? `${s.slice(0, maxLength - 1)}…` : s;
99
+ }
100
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/util/json.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Verifying a token the account system issued.
3
+ *
4
+ * The point is that no network call happens on the path of a request. A server
5
+ * that asks an auth service whether a token is good has put someone else's
6
+ * availability in front of every call it serves. A signature answers the same
7
+ * question from arithmetic.
8
+ *
9
+ * Lives in the engine because two services need it: the sync server, whose
10
+ * writes it authorises, and the metering gateway, which charges an account it
11
+ * has to identify first.
12
+ *
13
+ * Written by hand rather than pulled in, because the whole of what is needed
14
+ * is three signature checks and four claim checks, and this package has one
15
+ * dependency on purpose. The parts that are easy to get subtly wrong are
16
+ * called out where they happen.
17
+ */
18
+ export interface JwtOptions {
19
+ /** Shared secret for HS256 — Supabase calls this the project's JWT secret. */
20
+ secret?: string;
21
+ /** Endpoint serving public keys, for projects signing asymmetrically. */
22
+ jwksUrl?: string;
23
+ /** Required `iss`. Unset accepts any, which is right only behind one issuer. */
24
+ issuer?: string;
25
+ /** Required `aud`. Supabase issues `authenticated` for a signed-in user. */
26
+ audience?: string;
27
+ /** Claim carrying team ids. A Supabase auth hook is what puts it there. */
28
+ teamsClaim?: string;
29
+ /** Tolerance for clock drift between here and the issuer. */
30
+ clockSkewSeconds?: number;
31
+ /** Overridable for tests. */
32
+ fetch?: typeof globalThis.fetch;
33
+ }
34
+ export interface VerifiedClaims {
35
+ /** The account id. Supabase puts the user's uuid here. */
36
+ sub: string;
37
+ teams: string[];
38
+ /**
39
+ * The address on the account, when the issuer put one in the token. Never
40
+ * an identifier — `sub` is — but it saves asking someone for an email a
41
+ * signed token already states.
42
+ */
43
+ email?: string;
44
+ }
45
+ export declare class JwtVerifier {
46
+ private readonly opts;
47
+ private readonly http;
48
+ private keys;
49
+ private lastFetch;
50
+ private fetching;
51
+ constructor(opts: JwtOptions);
52
+ /** Whether anything is configured to verify against. */
53
+ get enabled(): boolean;
54
+ /**
55
+ * Claims, or undefined for anything that does not check out.
56
+ *
57
+ * Every failure is the same answer on purpose. Telling a caller whether the
58
+ * signature or the expiry was the problem is telling them how to get closer.
59
+ */
60
+ verify(token: string): Promise<VerifiedClaims | undefined>;
61
+ /**
62
+ * Check the signature against a key chosen by *kind*, never by the token.
63
+ *
64
+ * The classic break here is algorithm confusion: a token arrives claiming
65
+ * HS256, the server reaches for "the key" and finds an RSA public key, and
66
+ * HMACs with a value the attacker also has. So a symmetric alg is only ever
67
+ * checked against the configured secret and an asymmetric one only ever
68
+ * against a JWKS key, and the two paths cannot borrow each other's material.
69
+ */
70
+ private signatureValid;
71
+ private publicKey;
72
+ private refresh;
73
+ private claims;
74
+ }
@@ -0,0 +1,177 @@
1
+ import { createHmac, createPublicKey, timingSafeEqual, verify as verifySignature } from "node:crypto";
2
+ import { parseJson } from "./json.js";
3
+ const DEFAULT_SKEW_SECONDS = 60;
4
+ /** Floor between JWKS fetches, so an unknown kid cannot be used to hammer it. */
5
+ const JWKS_REFRESH_FLOOR_MS = 60_000;
6
+ export class JwtVerifier {
7
+ opts;
8
+ http;
9
+ keys = new Map();
10
+ lastFetch = 0;
11
+ fetching;
12
+ constructor(opts) {
13
+ this.opts = opts;
14
+ this.http = opts.fetch ?? globalThis.fetch;
15
+ }
16
+ /** Whether anything is configured to verify against. */
17
+ get enabled() {
18
+ return Boolean(this.opts.secret || this.opts.jwksUrl);
19
+ }
20
+ /**
21
+ * Claims, or undefined for anything that does not check out.
22
+ *
23
+ * Every failure is the same answer on purpose. Telling a caller whether the
24
+ * signature or the expiry was the problem is telling them how to get closer.
25
+ */
26
+ async verify(token) {
27
+ const parts = token.split(".");
28
+ if (parts.length !== 3)
29
+ return undefined;
30
+ const [rawHeader, rawPayload, rawSignature] = parts;
31
+ const header = decodeJson(rawHeader);
32
+ const payload = decodeJson(rawPayload);
33
+ if (!header || !payload)
34
+ return undefined;
35
+ const alg = typeof header.alg === "string" ? header.alg : "";
36
+ // `none` is a real algorithm in the spec and an unconditional forgery in
37
+ // practice. It is rejected by not appearing below, and named here so that
38
+ // nobody adds it back as a convenience.
39
+ if (!SUPPORTED.has(alg))
40
+ return undefined;
41
+ const signed = Buffer.from(`${rawHeader}.${rawPayload}`, "utf8");
42
+ const signature = decodeBase64Url(rawSignature);
43
+ if (!signature)
44
+ return undefined;
45
+ if (!(await this.signatureValid(alg, header.kid, signed, signature)))
46
+ return undefined;
47
+ return this.claims(payload);
48
+ }
49
+ /**
50
+ * Check the signature against a key chosen by *kind*, never by the token.
51
+ *
52
+ * The classic break here is algorithm confusion: a token arrives claiming
53
+ * HS256, the server reaches for "the key" and finds an RSA public key, and
54
+ * HMACs with a value the attacker also has. So a symmetric alg is only ever
55
+ * checked against the configured secret and an asymmetric one only ever
56
+ * against a JWKS key, and the two paths cannot borrow each other's material.
57
+ */
58
+ async signatureValid(alg, kid, signed, signature) {
59
+ if (alg === "HS256") {
60
+ if (!this.opts.secret)
61
+ return false;
62
+ const expected = createHmac("sha256", this.opts.secret).update(signed).digest();
63
+ return expected.length === signature.length && timingSafeEqual(expected, signature);
64
+ }
65
+ if (!this.opts.jwksUrl)
66
+ return false;
67
+ const entry = await this.publicKey(kid);
68
+ // The key's own algorithm decides, not the header's claim about it.
69
+ if (!entry || entry.alg !== alg)
70
+ return false;
71
+ if (alg === "RS256") {
72
+ return verifySignature("RSA-SHA256", signed, entry.key, signature);
73
+ }
74
+ // JWS carries ECDSA signatures as raw r||s, while Node defaults to DER.
75
+ // Without this the check fails for every valid ES256 token, which reads as
76
+ // "the key is wrong" and sends you looking in the wrong place.
77
+ return verifySignature("SHA256", signed, { key: entry.key, dsaEncoding: "ieee-p1363" }, signature);
78
+ }
79
+ async publicKey(kid) {
80
+ const held = kid ? this.keys.get(kid) : [...this.keys.values()][0];
81
+ if (held)
82
+ return held;
83
+ // A kid nobody has seen means the issuer rotated, which is worth one
84
+ // refetch — but not one per request, or an invented kid becomes a way to
85
+ // make this server hammer the issuer on demand.
86
+ if (Date.now() - this.lastFetch < JWKS_REFRESH_FLOOR_MS)
87
+ return undefined;
88
+ await this.refresh();
89
+ return kid ? this.keys.get(kid) : [...this.keys.values()][0];
90
+ }
91
+ refresh() {
92
+ this.fetching ??= (async () => {
93
+ this.lastFetch = Date.now();
94
+ try {
95
+ const response = await this.http(this.opts.jwksUrl, { redirect: "error" });
96
+ if (!response.ok)
97
+ return;
98
+ const body = parseJson(await response.text());
99
+ if (!body.ok || !Array.isArray(body.value.keys))
100
+ return;
101
+ const next = new Map();
102
+ for (const entry of body.value.keys) {
103
+ const jwk = entry;
104
+ if (!jwk || typeof jwk.kid !== "string")
105
+ continue;
106
+ if (jwk.use && jwk.use !== "sig")
107
+ continue;
108
+ const alg = typeof jwk.alg === "string" ? jwk.alg : jwk.kty === "RSA" ? "RS256" : "ES256";
109
+ if (!SUPPORTED.has(alg) || alg === "HS256")
110
+ continue;
111
+ try {
112
+ next.set(jwk.kid, {
113
+ key: createPublicKey({ key: jwk, format: "jwk" }),
114
+ alg,
115
+ });
116
+ }
117
+ catch {
118
+ // One unusable key is not a reason to drop the rest.
119
+ }
120
+ }
121
+ if (next.size > 0)
122
+ this.keys = next;
123
+ }
124
+ catch {
125
+ // Keep whatever was already loaded. A JWKS endpoint being briefly
126
+ // unreachable should not invalidate every token in flight.
127
+ }
128
+ finally {
129
+ this.fetching = undefined;
130
+ }
131
+ })();
132
+ return this.fetching;
133
+ }
134
+ claims(payload) {
135
+ const now = Math.floor(Date.now() / 1000);
136
+ const skew = this.opts.clockSkewSeconds ?? DEFAULT_SKEW_SECONDS;
137
+ const exp = payload["exp"];
138
+ // Required, not merely checked when present: a token with no expiry is a
139
+ // token that outlives the account it names.
140
+ if (typeof exp !== "number" || now > exp + skew)
141
+ return undefined;
142
+ const nbf = payload["nbf"];
143
+ if (typeof nbf === "number" && now + skew < nbf)
144
+ return undefined;
145
+ if (this.opts.issuer && payload["iss"] !== this.opts.issuer)
146
+ return undefined;
147
+ if (this.opts.audience && !audienceAllows(payload["aud"], this.opts.audience))
148
+ return undefined;
149
+ const sub = payload["sub"];
150
+ if (typeof sub !== "string" || !sub)
151
+ return undefined;
152
+ const raw = payload[this.opts.teamsClaim ?? "teams"];
153
+ const teams = Array.isArray(raw) ? raw.filter((id) => typeof id === "string") : [];
154
+ const email = payload["email"];
155
+ return { sub, teams, ...(typeof email === "string" && email ? { email } : {}) };
156
+ }
157
+ }
158
+ const SUPPORTED = new Set(["HS256", "RS256", "ES256"]);
159
+ /** `aud` is a string or an array of them, and either may be the match. */
160
+ function audienceAllows(claim, required) {
161
+ if (typeof claim === "string")
162
+ return claim === required;
163
+ return Array.isArray(claim) && claim.includes(required);
164
+ }
165
+ function decodeBase64Url(value) {
166
+ if (!/^[A-Za-z0-9_-]+$/.test(value))
167
+ return undefined;
168
+ return Buffer.from(value, "base64url");
169
+ }
170
+ function decodeJson(segment) {
171
+ const raw = decodeBase64Url(segment);
172
+ if (!raw)
173
+ return undefined;
174
+ const parsed = parseJson(raw.toString("utf8"));
175
+ return parsed.ok ? parsed.value : undefined;
176
+ }
177
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/util/jwt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error" | "silent";
2
+ export interface Logger {
3
+ level: LogLevel;
4
+ debug(msg: string, data?: unknown): void;
5
+ info(msg: string, data?: unknown): void;
6
+ warn(msg: string, data?: unknown): void;
7
+ error(msg: string, data?: unknown): void;
8
+ child(scope: string): Logger;
9
+ }
10
+ /** Where log lines go. Injected so a non-terminal frontend can capture them. */
11
+ export type LogSink = (line: string) => void;
12
+ export declare function createLogger(scope?: string, level?: LogLevel, sink?: LogSink): Logger;
13
+ export declare const noopLogger: Logger;
@@ -0,0 +1,59 @@
1
+ const ORDER = {
2
+ debug: 10,
3
+ info: 20,
4
+ warn: 30,
5
+ error: 40,
6
+ silent: 100,
7
+ };
8
+ function envLevel() {
9
+ const raw = process.env.OHARNESS_LOG_LEVEL?.toLowerCase();
10
+ if (raw && raw in ORDER)
11
+ return raw;
12
+ return "warn";
13
+ }
14
+ /**
15
+ * Diagnostics go to stderr, never stdout.
16
+ *
17
+ * This is the one place in the engine that writes to a stream, and it is
18
+ * deliberately stderr: stdout belongs to whatever frontend is rendering, and
19
+ * a library that swallows its own warnings by default is worse than one that
20
+ * puts them on the conventional diagnostic channel. `test/layering.test.ts`
21
+ * enforces both halves of that sentence.
22
+ */
23
+ const stderrSink = (line) => process.stderr.write(`${line}\n`);
24
+ export function createLogger(scope = "harness", level = envLevel(), sink = stderrSink) {
25
+ const write = (lvl, msg, data) => {
26
+ if (ORDER[lvl] < ORDER[logger.level])
27
+ return;
28
+ const line = `[${new Date().toISOString()}] ${lvl.toUpperCase().padEnd(5)} ${scope}: ${msg}`;
29
+ sink(data === undefined ? line : `${line} ${safeJson(data)}`);
30
+ };
31
+ const logger = {
32
+ level,
33
+ debug: (m, d) => write("debug", m, d),
34
+ info: (m, d) => write("info", m, d),
35
+ warn: (m, d) => write("warn", m, d),
36
+ error: (m, d) => write("error", m, d),
37
+ child: (sub) => createLogger(`${scope}:${sub}`, logger.level, sink),
38
+ };
39
+ return logger;
40
+ }
41
+ function safeJson(value) {
42
+ try {
43
+ return JSON.stringify(value);
44
+ }
45
+ catch {
46
+ return String(value);
47
+ }
48
+ }
49
+ export const noopLogger = {
50
+ level: "silent",
51
+ debug() { },
52
+ info() { },
53
+ warn() { },
54
+ error() { },
55
+ child() {
56
+ return noopLogger;
57
+ },
58
+ };
59
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAEA,MAAM,KAAK,GAA6B;IACtC,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,GAAG;CACZ,CAAC;AAWF,SAAS,QAAQ;IACf,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,WAAW,EAAE,CAAC;IAC1D,IAAI,GAAG,IAAI,GAAG,IAAI,KAAK;QAAE,OAAO,GAAe,CAAC;IAChD,OAAO,MAAM,CAAC;AAChB,CAAC;AAKD;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAY,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;AAExE,MAAM,UAAU,YAAY,CAC1B,KAAK,GAAG,SAAS,EACjB,QAAkB,QAAQ,EAAE,EAC5B,OAAgB,UAAU;IAE1B,MAAM,KAAK,GAAG,CAAC,GAAa,EAAE,GAAW,EAAE,IAAc,EAAE,EAAE;QAC3D,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,OAAO;QAC7C,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;QAC7F,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC;IAEF,MAAM,MAAM,GAAW;QACrB,KAAK;QACL,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACnC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QACrC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,KAAK,IAAI,GAAG,EAAE,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;KACpE,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAW;IAChC,KAAK,EAAE,QAAQ;IACf,KAAK,KAAI,CAAC;IACV,IAAI,KAAI,CAAC;IACT,IAAI,KAAI,CAAC;IACT,KAAK,KAAI,CAAC;IACV,KAAK;QACH,OAAO,UAAU,CAAC;IACpB,CAAC;CACF,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A push-driven async queue.
3
+ *
4
+ * Needed because several producers write events while one consumer reads them.
5
+ * The alternative — collecting into an array and yielding once everything
6
+ * finishes — turns "progress" into "a summary printed after the fact", and
7
+ * makes parallel work indistinguishable from serial work in a UI.
8
+ */
9
+ export declare class AsyncQueue<T> {
10
+ private readonly items;
11
+ private readonly waiters;
12
+ private closed;
13
+ private failure?;
14
+ /** Enqueue an item, handing it straight to a waiting consumer if there is one. */
15
+ push(item: T): void;
16
+ /** No more items. Consumers drain what is queued, then finish. */
17
+ close(): void;
18
+ /** Fail the stream. Queued items are dropped — the consumer must see the error. */
19
+ fail(error: unknown): void;
20
+ [Symbol.asyncIterator](): AsyncGenerator<T, void, void>;
21
+ }
22
+ /**
23
+ * Run several async generators concurrently, yielding events in the order they
24
+ * are produced rather than grouped by producer.
25
+ */
26
+ export declare function mergeStreams<T>(producers: Array<() => AsyncGenerator<T, void, void>>): AsyncGenerator<T, void, void>;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * A push-driven async queue.
3
+ *
4
+ * Needed because several producers write events while one consumer reads them.
5
+ * The alternative — collecting into an array and yielding once everything
6
+ * finishes — turns "progress" into "a summary printed after the fact", and
7
+ * makes parallel work indistinguishable from serial work in a UI.
8
+ */
9
+ export class AsyncQueue {
10
+ items = [];
11
+ waiters = [];
12
+ closed = false;
13
+ failure;
14
+ /** Enqueue an item, handing it straight to a waiting consumer if there is one. */
15
+ push(item) {
16
+ if (this.closed)
17
+ return;
18
+ const waiter = this.waiters.shift();
19
+ if (waiter)
20
+ waiter({ value: item, done: false });
21
+ else
22
+ this.items.push(item);
23
+ }
24
+ /** No more items. Consumers drain what is queued, then finish. */
25
+ close() {
26
+ if (this.closed)
27
+ return;
28
+ this.closed = true;
29
+ // Only waiters that arrived with an empty queue need waking; anything
30
+ // still buffered is delivered by the loop below first.
31
+ while (this.waiters.length > 0 && this.items.length === 0) {
32
+ this.waiters.shift()({ value: undefined, done: true });
33
+ }
34
+ }
35
+ /** Fail the stream. Queued items are dropped — the consumer must see the error. */
36
+ fail(error) {
37
+ if (this.closed)
38
+ return;
39
+ this.failure = error;
40
+ this.closed = true;
41
+ this.items.length = 0;
42
+ while (this.waiters.length > 0) {
43
+ this.waiters.shift()({ value: undefined, done: true });
44
+ }
45
+ }
46
+ async *[Symbol.asyncIterator]() {
47
+ while (true) {
48
+ if (this.failure !== undefined) {
49
+ const error = this.failure;
50
+ this.failure = undefined;
51
+ throw error;
52
+ }
53
+ if (this.items.length > 0) {
54
+ yield this.items.shift();
55
+ continue;
56
+ }
57
+ if (this.closed)
58
+ return;
59
+ const next = await new Promise((resolve) => {
60
+ this.waiters.push(resolve);
61
+ });
62
+ if (next.done) {
63
+ if (this.failure !== undefined) {
64
+ const error = this.failure;
65
+ this.failure = undefined;
66
+ throw error;
67
+ }
68
+ return;
69
+ }
70
+ yield next.value;
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * Run several async generators concurrently, yielding events in the order they
76
+ * are produced rather than grouped by producer.
77
+ */
78
+ export async function* mergeStreams(producers) {
79
+ if (producers.length === 0)
80
+ return;
81
+ if (producers.length === 1) {
82
+ yield* producers[0]();
83
+ return;
84
+ }
85
+ const queue = new AsyncQueue();
86
+ let running = producers.length;
87
+ for (const producer of producers) {
88
+ void (async () => {
89
+ try {
90
+ for await (const item of producer())
91
+ queue.push(item);
92
+ }
93
+ catch (err) {
94
+ queue.fail(err);
95
+ return;
96
+ }
97
+ finally {
98
+ running--;
99
+ if (running === 0)
100
+ queue.close();
101
+ }
102
+ })();
103
+ }
104
+ yield* queue;
105
+ }
106
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/util/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import type { JSONSchema } from "../core/types.ts";
3
+ /** Convert a zod schema to a JSON Schema object suitable for tool definitions. */
4
+ export declare function zodToJsonSchema(schema: z.ZodType): JSONSchema;
5
+ /**
6
+ * Every provider expects a top-level object schema for tool inputs, and every
7
+ * provider chokes on `$schema`/`$id` at the top level of a function parameter.
8
+ */
9
+ export declare function normalizeObjectSchema(schema: JSONSchema): JSONSchema;
10
+ export declare function sanitizeForGemini(schema: JSONSchema): JSONSchema;
11
+ /**
12
+ * OpenAI strict function calling requires `additionalProperties: false` and
13
+ * every property listed in `required`. We only apply it when asked, because it
14
+ * also forbids optional fields.
15
+ */
16
+ export declare function toStrictOpenAiSchema(schema: JSONSchema): JSONSchema;
17
+ export type { z };