@inflexa-ai/harness 0.2.0 → 0.4.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 (81) hide show
  1. package/README.md +48 -0
  2. package/dist/agents/conversation-agent.d.ts +3 -0
  3. package/dist/agents/conversation-agent.js +2 -2
  4. package/dist/agents/sandbox/shared.d.ts +3 -0
  5. package/dist/agents/sandbox/shared.js +2 -2
  6. package/dist/app/chat-turn.d.ts +3 -0
  7. package/dist/app/chat-turn.js +3 -1
  8. package/dist/app/synthesize-run.d.ts +3 -0
  9. package/dist/app/synthesize-run.js +3 -1
  10. package/dist/execution/artifact-metadata.d.ts +3 -0
  11. package/dist/execution/artifact-metadata.js +8 -2
  12. package/dist/execution/artifact-registration.d.ts +2 -1
  13. package/dist/execution/artifact-registration.js +8 -2
  14. package/dist/execution/ephemeral-runner.d.ts +3 -0
  15. package/dist/execution/ephemeral-runner.js +8 -4
  16. package/dist/execution/post-step-pipeline.d.ts +3 -0
  17. package/dist/execution/post-step-pipeline.js +24 -4
  18. package/dist/execution/reconcile-manifest.d.ts +7 -0
  19. package/dist/execution/reconcile-manifest.js +19 -5
  20. package/dist/execution/report-runner.d.ts +3 -0
  21. package/dist/execution/report-runner.js +4 -4
  22. package/dist/execution/step-summary.d.ts +3 -0
  23. package/dist/execution/step-summary.js +4 -2
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.js +5 -0
  26. package/dist/lib/chrome.d.ts +4 -1
  27. package/dist/lib/chrome.js +10 -7
  28. package/dist/lib/console-logger.d.ts +19 -0
  29. package/dist/lib/console-logger.js +62 -0
  30. package/dist/lib/logger.d.ts +88 -0
  31. package/dist/lib/logger.js +37 -0
  32. package/dist/lib/otel.d.ts +2 -1
  33. package/dist/lib/otel.js +8 -3
  34. package/dist/runtime/boot.d.ts +2 -2
  35. package/dist/runtime/boot.js +1 -1
  36. package/dist/runtime/connection-budget.d.ts +2 -2
  37. package/dist/runtime/connection-budget.js +5 -3
  38. package/dist/runtime/dbos.d.ts +7 -7
  39. package/dist/runtime/dbos.js +14 -11
  40. package/dist/runtime/shutdown.d.ts +2 -2
  41. package/dist/runtime/shutdown.js +4 -3
  42. package/dist/sandbox/create-sandbox.d.ts +41 -3
  43. package/dist/sandbox/create-sandbox.js +39 -19
  44. package/dist/sandbox/docker-client.d.ts +17 -2
  45. package/dist/sandbox/docker-client.js +39 -12
  46. package/dist/sandbox/k8s-client.d.ts +3 -0
  47. package/dist/sandbox/k8s-client.js +5 -4
  48. package/dist/sandbox/notification-sweep.d.ts +3 -3
  49. package/dist/sandbox/notification-sweep.js +4 -2
  50. package/dist/sandbox/reaper.d.ts +3 -3
  51. package/dist/sandbox/reaper.js +7 -4
  52. package/dist/sandbox/watchdog.d.ts +3 -3
  53. package/dist/sandbox/watchdog.js +8 -2
  54. package/dist/state/init.d.ts +2 -1
  55. package/dist/state/init.js +5 -6
  56. package/dist/tasks/data-profile.d.ts +5 -0
  57. package/dist/tasks/data-profile.js +24 -17
  58. package/dist/tools/bio/search-dgidb.d.ts +11 -1
  59. package/dist/tools/bio/search-dgidb.js +92 -78
  60. package/dist/tools/iterate-report.d.ts +3 -0
  61. package/dist/tools/iterate-report.js +3 -1
  62. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  63. package/dist/tools/research/literature-reviewer.js +2 -2
  64. package/dist/tools/workspace/execute-command.d.ts +3 -0
  65. package/dist/tools/workspace/execute-command.js +3 -1
  66. package/dist/workflows/execute-analysis.d.ts +3 -0
  67. package/dist/workflows/execute-analysis.js +11 -7
  68. package/dist/workflows/execute-target-assessment.d.ts +3 -0
  69. package/dist/workflows/execute-target-assessment.js +19 -15
  70. package/dist/workflows/sandbox-step.d.ts +9 -0
  71. package/dist/workflows/sandbox-step.js +39 -17
  72. package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
  73. package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
  74. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
  75. package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
  76. package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
  77. package/dist/workflows/target-assessment/phase5-persist.js +8 -3
  78. package/dist/workflows/target-assessment/progress.d.ts +2 -1
  79. package/dist/workflows/target-assessment/progress.js +4 -3
  80. package/dist/workspace/filesystem.js +163 -74
  81. package/package.json +95 -96
@@ -28,8 +28,8 @@
28
28
  * OUTSIDE `tryFs`, so only the discrete `fs.open` / `fs.read` / `fs.readFile`
29
29
  * calls become `err`.
30
30
  */
31
- import { open as fsOpen, readFile as fsReadFile, readdir as fsReaddir, stat as fsStat } from "node:fs/promises";
32
- import { createReadStream } from "node:fs";
31
+ import { lstat as fsLstat, open as fsOpen, readdir as fsReaddir, stat as fsStat } from "node:fs/promises";
32
+ import { constants as fsConstants } from "node:fs";
33
33
  import { createInterface } from "node:readline";
34
34
  import { join } from "node:path";
35
35
  import { ResultAsync, err, errAsync, ok } from "neverthrow";
@@ -123,10 +123,20 @@ export function createWorkspaceFilesystem(deps) {
123
123
  return okAsync({ kind: "out_of_scope" });
124
124
  }
125
125
  const absolute = resolved.absolute;
126
- return tryFs("workspace.list", () => fsReaddir(absolute, { withFileTypes: true }), { path: absolute, onAbsent: () => ({ notFound: true }) }).andThen((dirents) => {
127
- if ("notFound" in dirents)
128
- return okAsync({ kind: "not_found" });
129
- return collectEntries(absolute, dirents).map((entries) => ({ kind: "ok", entries }));
126
+ // `lstat` first so a symlinked directory leaf is refused, not
127
+ // followed: a plain `readdir` would list the target's entries,
128
+ // and a leaf swapped to an escaping symlink after the realpath
129
+ // check would leak an off-tree directory's names. Node has no
130
+ // fd-based readdir, so this narrows (does not fully close) the
131
+ // readdir race — the atomic guarantee is on the content reads.
132
+ return safeLstat(absolute).andThen((s) => {
133
+ if (s?.isSymbolicLink())
134
+ return okAsync({ kind: "out_of_scope" });
135
+ return tryFs("workspace.list", () => fsReaddir(absolute, { withFileTypes: true }), { path: absolute, onAbsent: () => ({ notFound: true }) }).andThen((dirents) => {
136
+ if ("notFound" in dirents)
137
+ return okAsync({ kind: "not_found" });
138
+ return collectEntries(absolute, dirents).map((entries) => ({ kind: "ok", entries }));
139
+ });
130
140
  });
131
141
  });
132
142
  },
@@ -135,9 +145,16 @@ export function createWorkspaceFilesystem(deps) {
135
145
  if (resolved.kind === "out_of_scope") {
136
146
  return okAsync({ kind: "out_of_scope" });
137
147
  }
138
- return safeStat(resolved.absolute).map((s) => {
148
+ // `lstat`, not `stat`: a leaf symlink is refused rather than
149
+ // followed — same policy as the read open's `O_NOFOLLOW`.
150
+ // Reporting a followed target's type/size would re-open the
151
+ // metadata side of the confused-deputy leak, and it is atomic
152
+ // (a single non-following syscall, no check/use gap).
153
+ return safeLstat(resolved.absolute).map((s) => {
139
154
  if (!s)
140
155
  return { kind: "not_found" };
156
+ if (s.isSymbolicLink())
157
+ return { kind: "out_of_scope" };
141
158
  return {
142
159
  kind: "ok",
143
160
  type: s.isDirectory() ? "directory" : "file",
@@ -160,29 +177,89 @@ function safeStat(absolute) {
160
177
  onAbsent: () => null,
161
178
  });
162
179
  }
163
- /** Stat each directory entry to attach a size; absence/I/O failures degrade to no size. */
180
+ /** Like {@link safeStat} but NEVER follows a leaf symlink (`lstat`) — the seam's
181
+ * read-side no-follow policy for the `stat`/`list` metadata surface. */
182
+ function safeLstat(absolute) {
183
+ return tryFs("workspace.lstat", () => fsLstat(absolute), {
184
+ path: absolute,
185
+ onAbsent: () => null,
186
+ });
187
+ }
188
+ /** Lstat each directory entry to attach a size; absence/I/O failures degrade to
189
+ * no size. `lstat` (not `stat`) so a symlink entry reports its own size, never
190
+ * its target's — a followed target could be off-tree. `d.isDirectory()` comes
191
+ * from the dirent, which does not follow, so a symlink is a plain `file` entry. */
164
192
  function collectEntries(dir, dirents) {
165
193
  return ResultAsync.combine(dirents.map((d) => {
166
194
  if (d.isDirectory())
167
195
  return okAsync({ name: d.name, type: "directory" });
168
- return safeStat(join(dir, d.name)).map((s) => ({ name: d.name, type: "file", size: s?.size ?? undefined }));
196
+ return safeLstat(join(dir, d.name)).map((s) => ({ name: d.name, type: "file", size: s?.size ?? undefined }));
169
197
  }));
170
198
  }
199
+ /**
200
+ * Read-only open flags that REFUSE to follow a final-component symlink.
201
+ * `classifyWithinRoot` validated a snapshot of the path; a symlink swapped into
202
+ * the leaf between that check and the open here would otherwise be followed
203
+ * off-tree, re-opening the read-exfiltration hole for the length of the race
204
+ * (CWE-367). `O_NOFOLLOW` makes the leaf open atomic — a symlinked final
205
+ * component fails `ELOOP` in the same syscall that opens it, so there is no
206
+ * check/use gap. `?? 0` keeps the mask valid on a platform that lacks the flag.
207
+ *
208
+ * This refuses EVERY leaf symlink, including an in-tree one the persistent check
209
+ * would allow: pure Node cannot express "follow only if it stays in-tree" (that
210
+ * needs `openat2(RESOLVE_BENEATH)`), so the atomic, race-free choice is to not
211
+ * follow the leaf at all. Hard-linked staged inputs are unaffected — a hard link
212
+ * is not a symlink. An intermediate-directory symlink is beyond this guard (Node
213
+ * exposes no `openat2`); the upstream realpath check still rejects the
214
+ * persistent case of that.
215
+ */
216
+ const READ_NO_FOLLOW = fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0);
217
+ /**
218
+ * Open a workspace file read-only without following a leaf symlink (see
219
+ * {@link READ_NO_FOLLOW}). A symlinked final component surfaces as `out_of_scope`
220
+ * — the same verdict a persistent escape gets — never as off-tree bytes. Every
221
+ * content read (`readWithCap`, `readHeadLines`, `readTailLines`) opens through
222
+ * here so the bytes it streams come from a non-symlink leaf. Absence /
223
+ * permission / other I/O throws flow the `FsError` channel unchanged.
224
+ */
225
+ function openReadNoFollow(absolute) {
226
+ return tryFs("workspace.openNoFollow", async () => {
227
+ try {
228
+ return { kind: "ok", fh: await fsOpen(absolute, READ_NO_FOLLOW) };
229
+ }
230
+ catch (cause) {
231
+ if (cause.code === "ELOOP")
232
+ return { kind: "out_of_scope" };
233
+ throw cause;
234
+ }
235
+ }, { path: absolute });
236
+ }
171
237
  function readWithCap(absolute, totalSize, maxBytes) {
172
- if (maxBytes !== undefined && totalSize > maxBytes) {
173
- return tryFs("workspace.readCapped", async () => {
174
- const fh = await fsOpen(absolute, "r");
238
+ return openReadNoFollow(absolute).andThen((opened) => {
239
+ if (opened.kind === "out_of_scope")
240
+ return okAsync({ kind: "out_of_scope" });
241
+ const fh = opened.fh;
242
+ if (maxBytes !== undefined && totalSize > maxBytes) {
243
+ return tryFs("workspace.readCapped", async () => {
244
+ try {
245
+ const buf = Buffer.alloc(maxBytes);
246
+ await fh.read(buf, 0, maxBytes, 0);
247
+ return { kind: "truncated", content: buf, totalSize };
248
+ }
249
+ finally {
250
+ await fh.close();
251
+ }
252
+ }, { path: absolute });
253
+ }
254
+ return tryFs("workspace.readFile", async () => {
175
255
  try {
176
- const buf = Buffer.alloc(maxBytes);
177
- await fh.read(buf, 0, maxBytes, 0);
178
- return { kind: "truncated", content: buf, totalSize };
256
+ return { kind: "ok", content: await fh.readFile(), truncated: false };
179
257
  }
180
258
  finally {
181
259
  await fh.close();
182
260
  }
183
261
  }, { path: absolute });
184
- }
185
- return tryFs("workspace.readFile", async () => ({ kind: "ok", content: await fsReadFile(absolute), truncated: false }), { path: absolute });
262
+ });
186
263
  }
187
264
  function capBuffer(content, maxBytes) {
188
265
  if (maxBytes !== undefined && content.length > maxBytes) {
@@ -204,43 +281,51 @@ function capBuffer(content, maxBytes) {
204
281
  * follow-up `safeStat` (a discrete `fs` call) flows the `FsError` channel.
205
282
  */
206
283
  function readHeadLines(absolute, headLines, maxBytes) {
207
- return new ResultAsync((async () => {
208
- const stream = createReadStream(absolute, { highWaterMark: 16 * 1024 });
209
- const rl = createInterface({ input: stream, crlfDelay: Infinity });
210
- const collected = [];
211
- let bytes = 0;
212
- let truncated = false;
213
- try {
214
- for await (const line of rl) {
215
- const lineBytes = Buffer.byteLength(line, "utf8") + 1;
216
- if (maxBytes !== undefined && bytes + lineBytes > maxBytes) {
217
- truncated = true;
218
- break;
284
+ return openReadNoFollow(absolute).andThen((opened) => {
285
+ if (opened.kind === "out_of_scope")
286
+ return okAsync({ kind: "out_of_scope" });
287
+ const fh = opened.fh;
288
+ return new ResultAsync((async () => {
289
+ // `autoClose: false` keeps the FileHandle ours to close — destroying
290
+ // the stream must not race a close against our own `fh.close()`.
291
+ const stream = fh.createReadStream({ highWaterMark: 16 * 1024, autoClose: false });
292
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
293
+ const collected = [];
294
+ let bytes = 0;
295
+ let truncated = false;
296
+ try {
297
+ for await (const line of rl) {
298
+ const lineBytes = Buffer.byteLength(line, "utf8") + 1;
299
+ if (maxBytes !== undefined && bytes + lineBytes > maxBytes) {
300
+ truncated = true;
301
+ break;
302
+ }
303
+ collected.push(line);
304
+ bytes += lineBytes;
305
+ if (collected.length >= headLines)
306
+ break;
219
307
  }
220
- collected.push(line);
221
- bytes += lineBytes;
222
- if (collected.length >= headLines)
223
- break;
224
308
  }
225
- }
226
- finally {
227
- rl.close();
228
- stream.destroy();
229
- }
230
- const content = Buffer.from(collected.join("\n"));
231
- if (truncated) {
232
- // Total size is unknown without a full stat; report it as a separate read.
233
- return (await safeStat(absolute)).map((s) => ({
234
- kind: "truncated",
235
- content,
236
- totalSize: s?.size ?? content.length,
237
- }));
238
- }
239
- // Whether the loop exhausted the file or hit the line cap, the agent asked
240
- // for exactly this window, so this is `ok` not `truncated`. The tool layer
241
- // reports `mode: "head"` so the agent knows it's a window.
242
- return ok({ kind: "ok", content, truncated: false });
243
- })());
309
+ finally {
310
+ rl.close();
311
+ stream.destroy();
312
+ await fh.close();
313
+ }
314
+ const content = Buffer.from(collected.join("\n"));
315
+ if (truncated) {
316
+ // Total size is unknown without a full stat; report it as a separate read.
317
+ return (await safeStat(absolute)).map((s) => ({
318
+ kind: "truncated",
319
+ content,
320
+ totalSize: s?.size ?? content.length,
321
+ }));
322
+ }
323
+ // Whether the loop exhausted the file or hit the line cap, the agent asked
324
+ // for exactly this window, so this is `ok` not `truncated`. The tool layer
325
+ // reports `mode: "head"` so the agent knows it's a window.
326
+ return ok({ kind: "ok", content, truncated: false });
327
+ })());
328
+ });
244
329
  }
245
330
  /**
246
331
  * Read the last `tailLines` lines via a windowed read from the end. The
@@ -252,30 +337,34 @@ function readHeadLines(absolute, headLines, maxBytes) {
252
337
  * slicing is pure and runs inside the same `fn`.
253
338
  */
254
339
  function readTailLines(absolute, totalSize, tailLines, maxBytes) {
255
- return tryFs("workspace.readTail", async () => {
256
- const windowSize = Math.min(totalSize, maxBytes ?? totalSize);
257
- const offset = totalSize - windowSize;
258
- const fh = await fsOpen(absolute, "r");
259
- try {
260
- const buf = Buffer.alloc(windowSize);
261
- if (windowSize > 0) {
262
- await fh.read(buf, 0, windowSize, offset);
340
+ return openReadNoFollow(absolute).andThen((opened) => {
341
+ if (opened.kind === "out_of_scope")
342
+ return okAsync({ kind: "out_of_scope" });
343
+ const fh = opened.fh;
344
+ return tryFs("workspace.readTail", async () => {
345
+ try {
346
+ const windowSize = Math.min(totalSize, maxBytes ?? totalSize);
347
+ const offset = totalSize - windowSize;
348
+ const buf = Buffer.alloc(windowSize);
349
+ if (windowSize > 0) {
350
+ await fh.read(buf, 0, windowSize, offset);
351
+ }
352
+ const allLines = buf.toString("utf8").split(/\r?\n/);
353
+ // Drop the partial leading line if the window doesn't cover the file start.
354
+ const candidateLines = offset > 0 ? allLines.slice(1) : allLines;
355
+ const sliced = candidateLines.length > tailLines ? candidateLines.slice(-tailLines) : candidateLines;
356
+ const content = Buffer.from(sliced.join("\n"));
357
+ const gotEnoughLines = sliced.length >= tailLines || offset === 0;
358
+ if (!gotEnoughLines) {
359
+ return { kind: "truncated", content, totalSize };
360
+ }
361
+ return { kind: "ok", content, truncated: false };
263
362
  }
264
- const allLines = buf.toString("utf8").split(/\r?\n/);
265
- // Drop the partial leading line if the window doesn't cover the file start.
266
- const candidateLines = offset > 0 ? allLines.slice(1) : allLines;
267
- const sliced = candidateLines.length > tailLines ? candidateLines.slice(-tailLines) : candidateLines;
268
- const content = Buffer.from(sliced.join("\n"));
269
- const gotEnoughLines = sliced.length >= tailLines || offset === 0;
270
- if (!gotEnoughLines) {
271
- return { kind: "truncated", content, totalSize };
363
+ finally {
364
+ await fh.close();
272
365
  }
273
- return { kind: "ok", content, truncated: false };
274
- }
275
- finally {
276
- await fh.close();
277
- }
278
- }, { path: absolute });
366
+ }, { path: absolute });
367
+ });
279
368
  }
280
369
  function sliceHeadLines(content, headLines, maxBytes) {
281
370
  const capped = maxBytes !== undefined && content.length > maxBytes ? content.subarray(0, maxBytes) : content;
package/package.json CHANGED
@@ -1,101 +1,100 @@
1
1
  {
2
- "name": "@inflexa-ai/harness",
3
- "version": "0.2.0",
4
- "description": "Host-agnostic bioinformatics agent harness — hand-rolled agent loop, DBOS-durable workflows, sandboxed compute.",
5
- "license": "Apache-2.0",
6
- "type": "module",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js"
2
+ "name": "@inflexa-ai/harness",
3
+ "version": "0.4.0",
4
+ "description": "Host-agnostic bioinformatics agent harness \u2014 hand-rolled agent loop, DBOS-durable workflows, sandboxed compute.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./*.js": {
15
+ "types": "./dist/*.d.ts",
16
+ "import": "./dist/*.js"
17
+ },
18
+ "./*": {
19
+ "types": "./dist/*.d.ts",
20
+ "import": "./dist/*.js"
21
+ }
13
22
  },
14
- "./*.js": {
15
- "types": "./dist/*.d.ts",
16
- "import": "./dist/*.js"
23
+ "files": [
24
+ "dist",
25
+ "NOTICE"
26
+ ],
27
+ "engines": {
28
+ "node": ">=24"
17
29
  },
18
- "./*": {
19
- "types": "./dist/*.d.ts",
20
- "import": "./dist/*.js"
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/inflexa-ai/inflexa.git",
33
+ "directory": "harness"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "format": "prettier --write src/",
38
+ "format:file": "prettier --write",
39
+ "lint": "eslint .",
40
+ "prepare": "tsc -p tsconfig.json",
41
+ "prepublishOnly": "bun run build && bun run smoke",
42
+ "smoke": "node scripts/smoke.mjs",
43
+ "typecheck": "tsc --noEmit"
44
+ },
45
+ "dependencies": {
46
+ "@ai-sdk/anthropic": "^4.0.15",
47
+ "@ai-sdk/openai-compatible": "^3.0.10",
48
+ "@ai-sdk/provider": "^4.0.3",
49
+ "@anthropic-ai/sdk": "^0.107.0",
50
+ "@dbos-inc/dbos-sdk": "^4.23.6",
51
+ "@kubernetes/client-node": "^1.4.0",
52
+ "@opentelemetry/api": "^1.9.1",
53
+ "@opentelemetry/core": "^2.9.0",
54
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.219.0",
55
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.219.0",
56
+ "@opentelemetry/resources": "^2.9.0",
57
+ "@opentelemetry/sdk-metrics": "^2.9.0",
58
+ "@opentelemetry/sdk-trace-base": "^2.9.0",
59
+ "@opentelemetry/sdk-trace-node": "^2.9.0",
60
+ "@opentelemetry/semantic-conventions": "^1.43.0",
61
+ "ai": "^7.0.28",
62
+ "cheerio": "^1.2.0",
63
+ "dockerode": "^4.0.12",
64
+ "fast-xml-parser": "^5.10.0",
65
+ "hono": "^4.12.30",
66
+ "js-tiktoken": "^1.0.21",
67
+ "mime": "^4.1.0",
68
+ "nanoid": "^5.1.16",
69
+ "neverthrow": "^8.2.0",
70
+ "nunjucks": "^3.2.4",
71
+ "openai": "^4.104.0",
72
+ "pdf-parse": "^2.4.5",
73
+ "pg": "^8.22.0",
74
+ "puppeteer-core": "^23.11.1",
75
+ "zod": "^4.4.3"
76
+ },
77
+ "devDependencies": {
78
+ "@eslint/js": "^10.0.1",
79
+ "@types/bun": "latest",
80
+ "@types/dockerode": "^3.3.47",
81
+ "@types/node": "^24.13.3",
82
+ "@types/nunjucks": "^3.2.6",
83
+ "@types/pdf-parse": "^1.1.5",
84
+ "@types/pg": "^8.20.0",
85
+ "eslint": "^10.7.0",
86
+ "eslint-plugin-neverthrow": "^1.1.4",
87
+ "globals": "^17.7.0",
88
+ "prettier": "^3.9.5",
89
+ "testcontainers": "^10.28.0",
90
+ "typescript": "^6.0.3",
91
+ "typescript-eslint": "^8.64.0"
92
+ },
93
+ "publishConfig": {
94
+ "access": "public",
95
+ "registry": "https://registry.npmjs.org"
96
+ },
97
+ "patchedDependencies": {
98
+ "eslint-plugin-neverthrow@1.1.4": "patches/eslint-plugin-neverthrow@1.1.4.patch"
21
99
  }
22
- },
23
- "files": [
24
- "dist",
25
- "NOTICE"
26
- ],
27
- "engines": {
28
- "node": ">=24"
29
- },
30
- "repository": {
31
- "type": "git",
32
- "url": "https://github.com/inflexa-ai/inflexa.git",
33
- "directory": "harness"
34
- },
35
- "scripts": {
36
- "build": "tsc -p tsconfig.json",
37
- "format": "prettier --write src/",
38
- "format:file": "prettier --write",
39
- "lint": "eslint .",
40
- "prepare": "tsc -p tsconfig.json",
41
- "prepublishOnly": "bun run build && bun run smoke",
42
- "smoke": "node scripts/smoke.mjs",
43
- "typecheck": "tsc --noEmit"
44
- },
45
- "dependencies": {
46
- "@ai-sdk/anthropic": "^4.0.15",
47
- "@ai-sdk/openai-compatible": "^3.0.10",
48
- "@ai-sdk/provider": "^4.0.3",
49
- "@anthropic-ai/sdk": "^0.107.0",
50
- "@dbos-inc/dbos-sdk": "^4.23.6",
51
- "@kubernetes/client-node": "^1.4.0",
52
- "@opentelemetry/api": "^1.9.1",
53
- "@opentelemetry/core": "^2.9.0",
54
- "@opentelemetry/exporter-metrics-otlp-http": "^0.219.0",
55
- "@opentelemetry/exporter-trace-otlp-proto": "^0.219.0",
56
- "@opentelemetry/resources": "^2.9.0",
57
- "@opentelemetry/sdk-metrics": "^2.9.0",
58
- "@opentelemetry/sdk-trace-base": "^2.9.0",
59
- "@opentelemetry/sdk-trace-node": "^2.9.0",
60
- "@opentelemetry/semantic-conventions": "^1.43.0",
61
- "ai": "^7.0.28",
62
- "cheerio": "^1.2.0",
63
- "dockerode": "^4.0.12",
64
- "fast-xml-parser": "^5.10.0",
65
- "hono": "^4.12.30",
66
- "js-tiktoken": "^1.0.21",
67
- "mime": "^4.1.0",
68
- "nanoid": "^5.1.16",
69
- "neverthrow": "^8.2.0",
70
- "nunjucks": "^3.2.4",
71
- "openai": "^4.104.0",
72
- "pdf-parse": "^2.4.5",
73
- "pg": "^8.22.0",
74
- "pino": "^10.3.1",
75
- "puppeteer-core": "^23.11.1",
76
- "zod": "^4.4.3"
77
- },
78
- "devDependencies": {
79
- "@eslint/js": "^10.0.1",
80
- "@types/bun": "latest",
81
- "@types/dockerode": "^3.3.47",
82
- "@types/node": "^24.13.3",
83
- "@types/nunjucks": "^3.2.6",
84
- "@types/pdf-parse": "^1.1.5",
85
- "@types/pg": "^8.20.0",
86
- "eslint": "^10.7.0",
87
- "eslint-plugin-neverthrow": "^1.1.4",
88
- "globals": "^17.7.0",
89
- "prettier": "^3.9.5",
90
- "testcontainers": "^10.28.0",
91
- "typescript": "^6.0.3",
92
- "typescript-eslint": "^8.64.0"
93
- },
94
- "publishConfig": {
95
- "access": "public",
96
- "registry": "https://registry.npmjs.org"
97
- },
98
- "patchedDependencies": {
99
- "eslint-plugin-neverthrow@1.1.4": "patches/eslint-plugin-neverthrow@1.1.4.patch"
100
- }
101
100
  }