@inflexa-ai/harness 0.47.0 → 0.48.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 +1 -1
- package/dist/agents/conversation-agent.d.ts +6 -0
- package/dist/agents/conversation-agent.js +2 -0
- package/dist/agents/report-session-agent.d.ts +3 -0
- package/dist/agents/report-session-agent.js +13 -12
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/loop/chat-stream.js +2 -1
- package/dist/loop/types.d.ts +5 -0
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +7 -5
- package/dist/runtime/assemble.d.ts +13 -4
- package/dist/runtime/assemble.js +2 -0
- package/dist/state/index.d.ts +2 -2
- package/dist/state/index.js +1 -1
- package/dist/state/step-executions.d.ts +13 -0
- package/dist/state/step-executions.js +22 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +2 -2
- package/dist/tools/report-authoring/authoring-tools.js +31 -6
- package/dist/tools/report-session/script-tools.d.ts +3 -0
- package/dist/tools/report-session/script-tools.js +10 -1
- package/dist/tools/script/archive.d.ts +34 -0
- package/dist/tools/script/archive.js +182 -0
- package/dist/tools/script/file-tools.d.ts +10 -4
- package/dist/tools/script/file-tools.js +156 -42
- package/dist/tools/script/metrics.d.ts +10 -1
- package/dist/tools/script/metrics.js +17 -2
- package/dist/tools/script/run-script.js +91 -4
- package/dist/tools/script/scratch.d.ts +3 -0
- package/dist/tools/script/scratch.js +7 -4
- package/dist/tools/workspace/mutator.d.ts +3 -0
- package/dist/tools/workspace/mutator.js +11 -1
- package/dist/workspace/session-files.d.ts +35 -0
- package/dist/workspace/session-files.js +16 -0
- package/package.json +2 -1
|
@@ -33,6 +33,8 @@ const SCRIPT_TIMEOUT_MS = 180_000;
|
|
|
33
33
|
const SCRIPT_MEMORY_BYTES = 256 * 1024 * 1024;
|
|
34
34
|
/** The text of a result that reaches the model. A longer output keeps its head and its tail. */
|
|
35
35
|
const MAX_RESULT_CHARS = 24_000;
|
|
36
|
+
/** The least time between two live copies of the nested calls of a script. */
|
|
37
|
+
const LIVE_ACTIVITY_INTERVAL_MS = 500;
|
|
36
38
|
const inputSchema = z.object({
|
|
37
39
|
description: z.string().max(200).optional().describe("One short line that tells the user what the script does, for example 'Top 20 genes by padj'."),
|
|
38
40
|
code: z
|
|
@@ -57,11 +59,59 @@ function plainJson(value) {
|
|
|
57
59
|
return value === undefined ? null : parseJsonAs(JSON.stringify(value), z.json());
|
|
58
60
|
}
|
|
59
61
|
function describeTool(tool) {
|
|
60
|
-
return renderToolSample({ name: tool.id, description: tool.description, inputSchema: tool
|
|
62
|
+
return renderToolSample({ name: tool.id, description: tool.description, inputSchema: inputSchemaOf(tool), outputSchema: tool.outputJsonSchema });
|
|
61
63
|
}
|
|
62
64
|
function issuesOf(error) {
|
|
63
65
|
return error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ");
|
|
64
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The live copies of the activity part of one script, so a reader sees each nested call while the script runs. A copy
|
|
69
|
+
* is transient, and the part that the call emits at its end is the one that a stored display keeps. Each copy holds
|
|
70
|
+
* each entry so far, thus at most one copy goes in each interval. A copy that the interval holds back goes at the end
|
|
71
|
+
* of the interval, thus a long nested call shows before it ends.
|
|
72
|
+
*/
|
|
73
|
+
function createLiveActivity(emit, log, intervalMs) {
|
|
74
|
+
let latest = null;
|
|
75
|
+
let lastSentMs = Number.NEGATIVE_INFINITY;
|
|
76
|
+
let timer;
|
|
77
|
+
let closed = false;
|
|
78
|
+
const inFlight = new Set();
|
|
79
|
+
const send = () => {
|
|
80
|
+
timer = undefined;
|
|
81
|
+
if (closed || latest === null)
|
|
82
|
+
return;
|
|
83
|
+
lastSentMs = Date.now();
|
|
84
|
+
const part = latest;
|
|
85
|
+
const sent = (async () => {
|
|
86
|
+
try {
|
|
87
|
+
await emit({ type: part.type, data: part, transient: true });
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
log.warn("a live copy of the nested calls did not reach the stream", log.errorFields(err));
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
inFlight.add(sent);
|
|
94
|
+
void sent.finally(() => inFlight.delete(sent));
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
update(part) {
|
|
98
|
+
latest = part;
|
|
99
|
+
if (closed || timer !== undefined)
|
|
100
|
+
return;
|
|
101
|
+
const wait = lastSentMs + intervalMs - Date.now();
|
|
102
|
+
if (wait <= 0)
|
|
103
|
+
send();
|
|
104
|
+
// oxlint-disable-next-line eslint-js/no-restricted-syntax -- `close` clears it, and the call runs `close` in its `finally`, thus it ends with the call.
|
|
105
|
+
else
|
|
106
|
+
timer = setTimeout(send, wait);
|
|
107
|
+
},
|
|
108
|
+
async close() {
|
|
109
|
+
closed = true;
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
await Promise.all(inFlight);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
65
115
|
/** Keep the files of the call record of one nested call. A record of another shape carries no file. */
|
|
66
116
|
function collectEffect(state, value) {
|
|
67
117
|
const effect = ScriptFileEffectSchema.safeParse(readToolCallRecord(value));
|
|
@@ -82,7 +132,7 @@ function scriptToolOf(tool, ctx, state, log, inCallContext) {
|
|
|
82
132
|
return {
|
|
83
133
|
name: tool.id,
|
|
84
134
|
description: tool.description,
|
|
85
|
-
inputSchema: tool
|
|
135
|
+
inputSchema: inputSchemaOf(tool),
|
|
86
136
|
outputSchema: tool.outputJsonSchema,
|
|
87
137
|
execute: (args, options) => inCallContext(() => runNestedCall(tool, ctx, state, log, args, options.signal)),
|
|
88
138
|
};
|
|
@@ -148,6 +198,36 @@ function withoutProse(schema) {
|
|
|
148
198
|
.filter(([key]) => key !== "description" && key !== "examples")
|
|
149
199
|
.map(([key, value]) => [key, withoutProse(value)]));
|
|
150
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* A JSON schema as a script reads it. A tool can union its real shape with the empty schema to keep its runtime parse
|
|
203
|
+
* permissive (`add_block` does, thus a malformed block reaches its typed refusal). The type of such a union is
|
|
204
|
+
* `unknown`, which hides the real shape, thus the empty member goes. The VM only renders the schema and validates
|
|
205
|
+
* nothing, and the tool still parses each input with its own schema.
|
|
206
|
+
*/
|
|
207
|
+
function printableSchema(schema) {
|
|
208
|
+
const array = UnknownArraySchema.safeParse(schema);
|
|
209
|
+
if (array.success)
|
|
210
|
+
return array.data.map(printableSchema);
|
|
211
|
+
const record = parseUnknownRecord(schema);
|
|
212
|
+
if (record === undefined)
|
|
213
|
+
return schema;
|
|
214
|
+
let out = Object.fromEntries(Object.entries(record).map(([key, value]) => [key, printableSchema(value)]));
|
|
215
|
+
for (const key of ["anyOf", "oneOf"]) {
|
|
216
|
+
const members = UnknownArraySchema.safeParse(out[key]);
|
|
217
|
+
if (!members.success)
|
|
218
|
+
continue;
|
|
219
|
+
const shaped = members.data.filter((member) => Object.keys(parseUnknownRecord(member) ?? {}).length > 0);
|
|
220
|
+
if (shaped.length === 0 || shaped.length === members.data.length)
|
|
221
|
+
continue;
|
|
222
|
+
const rest = Object.fromEntries(Object.entries(out).filter(([name]) => name !== key));
|
|
223
|
+
out = shaped.length > 1 ? { ...rest, [key]: shaped } : { ...rest, ...parseUnknownRecord(shaped[0]) };
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
/** The input schema of a tool as a script reads it. See {@link printableSchema}. */
|
|
228
|
+
function inputSchemaOf(tool) {
|
|
229
|
+
return parseUnknownRecord(printableSchema(tool.jsonSchema)) ?? tool.jsonSchema;
|
|
230
|
+
}
|
|
151
231
|
/** The longest result type that the catalog shows. A longer type shows as `unknown`, and `describe()` gives it whole. */
|
|
152
232
|
const MAX_CATALOG_RESULT_TYPE_CHARS = 800;
|
|
153
233
|
/** One catalog entry: the first sentence of the description, then the signature with no prose. */
|
|
@@ -155,7 +235,7 @@ function catalogEntry(tool) {
|
|
|
155
235
|
const output = tool.outputJsonSchema === undefined ? undefined : parseUnknownRecord(withoutProse(tool.outputJsonSchema));
|
|
156
236
|
const resultType = output === undefined ? "unknown" : schemaToType(output, { maxChars: MAX_CATALOG_RESULT_TYPE_CHARS });
|
|
157
237
|
// pi renders a signature with no result schema as `...: Promise<unknown>;`, thus the catalog puts its capped type there.
|
|
158
|
-
const untyped = renderToolSignature({ name: tool.id, inputSchema: parseUnknownRecord(withoutProse(tool
|
|
238
|
+
const untyped = renderToolSignature({ name: tool.id, inputSchema: parseUnknownRecord(withoutProse(inputSchemaOf(tool))) ?? {} });
|
|
159
239
|
const signature = `${untyped.slice(0, -"unknown>;".length)}${resultType}>;`;
|
|
160
240
|
return `/** ${firstSentence(tool.description)} */\n${signature}`;
|
|
161
241
|
}
|
|
@@ -234,6 +314,7 @@ export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
|
234
314
|
source: { agentId: caller.agentId, callPath: [...caller.callPath] },
|
|
235
315
|
}, Date.now());
|
|
236
316
|
const tracked = { part: null };
|
|
317
|
+
const live = createLiveActivity(ctx.emit, log, LIVE_ACTIVITY_INTERVAL_MS);
|
|
237
318
|
let started = 0;
|
|
238
319
|
const state = {
|
|
239
320
|
code,
|
|
@@ -244,7 +325,11 @@ export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
|
244
325
|
nextInvocationId: () => `${ctx.invocationId}:${++started}`,
|
|
245
326
|
source: { agentId: RUN_SCRIPT_TOOL_ID, callPath: [...caller.callPath, RUN_SCRIPT_TOOL_ID], invocationId: ctx.invocationId },
|
|
246
327
|
observe: (frame) => {
|
|
247
|
-
|
|
328
|
+
const part = tracker.track(frame, Date.now());
|
|
329
|
+
if (part === null)
|
|
330
|
+
return;
|
|
331
|
+
tracked.part = part;
|
|
332
|
+
live.update(part);
|
|
248
333
|
},
|
|
249
334
|
};
|
|
250
335
|
// The VM calls a tool from a message event of its worker, and such an event runs outside the async context
|
|
@@ -286,6 +371,7 @@ export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
|
286
371
|
reads: state.reads.length,
|
|
287
372
|
writes: state.writes.length,
|
|
288
373
|
});
|
|
374
|
+
await live.close();
|
|
289
375
|
if (tracked.part !== null)
|
|
290
376
|
await ctx.emit({ type: tracked.part.type, data: tracked.part });
|
|
291
377
|
if (state.writes.length > 0) {
|
|
@@ -319,6 +405,7 @@ export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
|
319
405
|
return ok(record.run === undefined && record.nested.length === 0 ? outcome : withToolCallRecord(outcome, record));
|
|
320
406
|
}
|
|
321
407
|
finally {
|
|
408
|
+
await live.close();
|
|
322
409
|
await sandbox.close();
|
|
323
410
|
}
|
|
324
411
|
},
|
|
@@ -13,6 +13,7 @@ import type { AgentSession } from "../../auth/types.js";
|
|
|
13
13
|
import type { Logger } from "../../lib/logger.js";
|
|
14
14
|
import { type ProvenanceSeam } from "../../provenance/seam.js";
|
|
15
15
|
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
16
|
+
import { type SyncSessionFile } from "../../workspace/session-files.js";
|
|
16
17
|
/** The cap of one derived file. It matches the cap of a table read, thus a script can read back what it wrote. */
|
|
17
18
|
export declare const DERIVED_FILE_CAP_BYTES: number;
|
|
18
19
|
/** The cap of the derived files of one session together. */
|
|
@@ -76,6 +77,8 @@ export type ScriptScratchDeps = {
|
|
|
76
77
|
readonly sessionDir: (threadId: string) => string;
|
|
77
78
|
/** The provenance seam. An unbound session emit records nothing, and the scripts run the same. */
|
|
78
79
|
readonly provenance?: ProvenanceSeam;
|
|
80
|
+
/** The host copy of each file that lands, owned by the thread of the session. */
|
|
81
|
+
readonly syncSessionFile?: SyncSessionFile;
|
|
79
82
|
readonly logger?: Logger;
|
|
80
83
|
};
|
|
81
84
|
export declare function createScriptScratch(deps: ScriptScratchDeps): ScriptScratch;
|
|
@@ -17,6 +17,7 @@ import { tryFs } from "../../lib/fs-result.js";
|
|
|
17
17
|
import { unwrapOrThrow } from "../../lib/result.js";
|
|
18
18
|
import { bindSessionEmit } from "../../provenance/seam.js";
|
|
19
19
|
import { isSafeId } from "../../workspace/paths.js";
|
|
20
|
+
import { syncWrittenFile } from "../../workspace/session-files.js";
|
|
20
21
|
import { confinedWrite } from "../workspace/mutator.js";
|
|
21
22
|
/** The cap of one derived file. It matches the cap of a table read, thus a script can read back what it wrote. */
|
|
22
23
|
export const DERIVED_FILE_CAP_BYTES = 16 * 1024 * 1024;
|
|
@@ -55,7 +56,7 @@ async function directoryBytes(absolute) {
|
|
|
55
56
|
export function createScriptScratch(deps) {
|
|
56
57
|
const logger = (deps.logger ?? createNoopLogger()).named("script-scratch");
|
|
57
58
|
const observe = bindSessionEmit(deps.provenance, logger);
|
|
58
|
-
async function land(session, dir, name, content, invocationId) {
|
|
59
|
+
async function land(session, threadId, dir, name, content, invocationId) {
|
|
59
60
|
const { analysisId } = session.scope;
|
|
60
61
|
const workspaceRoot = await deps.resolveWorkspaceRoot(analysisId);
|
|
61
62
|
const landed = await confinedWrite({
|
|
@@ -70,14 +71,16 @@ export function createScriptScratch(deps) {
|
|
|
70
71
|
});
|
|
71
72
|
if (landed.status !== "ok")
|
|
72
73
|
return { refused: `the write of ${dir}/${name} was refused (${landed.status})` };
|
|
73
|
-
|
|
74
|
+
const file = { path: landed.relative, hash: computeSha256(landed.bytes), size: landed.bytes.length };
|
|
75
|
+
await syncWrittenFile(deps.syncSessionFile, logger, { session, owner: { kind: "thread", threadId }, ...file, content: landed.bytes });
|
|
76
|
+
return file;
|
|
74
77
|
}
|
|
75
78
|
return {
|
|
76
79
|
async saveScript({ session, invocationId, code }) {
|
|
77
80
|
const { threadId } = session.scope;
|
|
78
81
|
if (threadId === undefined)
|
|
79
82
|
return undefined;
|
|
80
|
-
const saved = await land(session, `${deps.sessionDir(threadId)}/scripts`, scriptFileName(invocationId), code, invocationId);
|
|
83
|
+
const saved = await land(session, threadId, `${deps.sessionDir(threadId)}/scripts`, scriptFileName(invocationId), code, invocationId);
|
|
81
84
|
if ("refused" in saved)
|
|
82
85
|
throw new Error(saved.refused);
|
|
83
86
|
return saved;
|
|
@@ -115,7 +118,7 @@ export function createScriptScratch(deps) {
|
|
|
115
118
|
if (used + bytes > DERIVED_SESSION_CAP_BYTES) {
|
|
116
119
|
return { status: "refused", reason: `the derived files of this session would pass the cap of ${String(DERIVED_SESSION_CAP_BYTES)} bytes` };
|
|
117
120
|
}
|
|
118
|
-
const landed = await land(session, dir, name, content, invocationId);
|
|
121
|
+
const landed = await land(session, threadId, dir, name, content, invocationId);
|
|
119
122
|
if ("refused" in landed)
|
|
120
123
|
return { status: "refused", reason: landed.refused };
|
|
121
124
|
return { status: "written", file: landed };
|
|
@@ -38,6 +38,7 @@ import type { AgentSession } from "../../auth/types.js";
|
|
|
38
38
|
import type { Logger } from "../../lib/logger.js";
|
|
39
39
|
import type { ProvenanceCollector } from "../../provenance/collector.js";
|
|
40
40
|
import { type ProvenanceSeam } from "../../provenance/seam.js";
|
|
41
|
+
import { type SyncSessionFile } from "../../workspace/session-files.js";
|
|
41
42
|
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
42
43
|
/** Outcome of a confined write. Expected outcomes are data variants — never throws on them. */
|
|
43
44
|
export type WriteFileResult = {
|
|
@@ -191,6 +192,8 @@ export type SessionWorkspaceMutatorDeps = {
|
|
|
191
192
|
* proceeds unchanged.
|
|
192
193
|
*/
|
|
193
194
|
readonly provenance?: ProvenanceSeam;
|
|
195
|
+
/** The host copy of each file that lands, owned by the analysis: the path is anywhere in its tree. */
|
|
196
|
+
readonly syncSessionFile?: SyncSessionFile;
|
|
194
197
|
readonly logger?: Logger;
|
|
195
198
|
};
|
|
196
199
|
/**
|
|
@@ -43,6 +43,7 @@ import { computeSha256 } from "../../lib/fs-helpers.js";
|
|
|
43
43
|
import { tryFs, tryFsWrite } from "../../lib/fs-result.js";
|
|
44
44
|
import { unwrapOrThrow } from "../../lib/result.js";
|
|
45
45
|
import { bindSessionEmit } from "../../provenance/seam.js";
|
|
46
|
+
import { syncWrittenFile } from "../../workspace/session-files.js";
|
|
46
47
|
import { resolveForWrite, STAGED_INPUT_ROOT } from "../../workspace/paths.js";
|
|
47
48
|
/**
|
|
48
49
|
* Agent-visible name of the tool driving a confined write. Rides the write args
|
|
@@ -305,7 +306,16 @@ export function createSessionWorkspaceMutator(deps) {
|
|
|
305
306
|
});
|
|
306
307
|
if (landed.status !== "ok")
|
|
307
308
|
return { ...landed, path };
|
|
308
|
-
|
|
309
|
+
const write = landedWrite(landed);
|
|
310
|
+
await syncWrittenFile(deps.syncSessionFile, logger, {
|
|
311
|
+
session,
|
|
312
|
+
owner: { kind: "analysis" },
|
|
313
|
+
path: write.record.path,
|
|
314
|
+
hash: write.record.hash,
|
|
315
|
+
size: write.record.size,
|
|
316
|
+
content: landed.bytes,
|
|
317
|
+
});
|
|
318
|
+
return write;
|
|
309
319
|
},
|
|
310
320
|
recordWrite(record) {
|
|
311
321
|
const event = {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files that a session writes outside a run, and the host hook that keeps a copy of each.
|
|
3
|
+
*
|
|
4
|
+
* A run registers its step files through `ArtifactRegistry`. A chat thread and a report session write files too:
|
|
5
|
+
* the scripts and the derived files of `run_script`, and the files of `write_file` and `edit_file`. A host that
|
|
6
|
+
* keeps the files of an analysis elsewhere binds `syncSessionFile`, and each write reaches it with its bytes. The
|
|
7
|
+
* call runs inside the tool step of the write, thus a step that runs again after a crash calls the hook again with
|
|
8
|
+
* the same file.
|
|
9
|
+
*/
|
|
10
|
+
import type { ResultAsync } from "neverthrow";
|
|
11
|
+
import type { AgentSession } from "../auth/types.js";
|
|
12
|
+
import { type NoticeFailure } from "../lib/hooks.js";
|
|
13
|
+
import type { Logger } from "../lib/logger.js";
|
|
14
|
+
/** Who owns a session file. A file in the scratch directory of a thread belongs to that thread. */
|
|
15
|
+
export type SessionFileOwner = {
|
|
16
|
+
readonly kind: "thread";
|
|
17
|
+
readonly threadId: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "analysis";
|
|
20
|
+
};
|
|
21
|
+
export type SessionFileWrite = {
|
|
22
|
+
/** The session of the tool call that wrote the file. */
|
|
23
|
+
readonly session: AgentSession;
|
|
24
|
+
readonly owner: SessionFileOwner;
|
|
25
|
+
/** The analysis-relative path, and the hash and the size of the bytes. */
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly hash: string;
|
|
28
|
+
readonly size: number;
|
|
29
|
+
/** The exact bytes that landed. */
|
|
30
|
+
readonly content: Uint8Array;
|
|
31
|
+
};
|
|
32
|
+
/** A notice: a failure is logged, and the write stays done. */
|
|
33
|
+
export type SyncSessionFile = (write: SessionFileWrite) => ResultAsync<void, NoticeFailure>;
|
|
34
|
+
/** Give one landed write to the hook of the host. An unbound hook does nothing. */
|
|
35
|
+
export declare function syncWrittenFile(sync: SyncSessionFile | undefined, logger: Logger, write: SessionFileWrite): Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files that a session writes outside a run, and the host hook that keeps a copy of each.
|
|
3
|
+
*
|
|
4
|
+
* A run registers its step files through `ArtifactRegistry`. A chat thread and a report session write files too:
|
|
5
|
+
* the scripts and the derived files of `run_script`, and the files of `write_file` and `edit_file`. A host that
|
|
6
|
+
* keeps the files of an analysis elsewhere binds `syncSessionFile`, and each write reaches it with its bytes. The
|
|
7
|
+
* call runs inside the tool step of the write, thus a step that runs again after a crash calls the hook again with
|
|
8
|
+
* the same file.
|
|
9
|
+
*/
|
|
10
|
+
import { deliverNotice } from "../lib/hooks.js";
|
|
11
|
+
/** Give one landed write to the hook of the host. An unbound hook does nothing. */
|
|
12
|
+
export async function syncWrittenFile(sync, logger, write) {
|
|
13
|
+
if (sync === undefined)
|
|
14
|
+
return;
|
|
15
|
+
await deliverNotice(logger.with({ analysisId: write.session.scope.analysisId, path: write.path }), "syncSessionFile", sync(write));
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inflexa-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Host-agnostic bioinformatics agent harness — an agent loop on the AI SDK, DBOS-durable workflows, sandboxed compute.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"dockerode": "^4.0.12",
|
|
84
84
|
"echarts": "6.1.0",
|
|
85
85
|
"fast-xml-parser": "^5.10.1",
|
|
86
|
+
"fflate": "^0.8.3",
|
|
86
87
|
"hono": "^4.12.34",
|
|
87
88
|
"hyparquet": "^1.28.1",
|
|
88
89
|
"js-tiktoken": "^1.0.21",
|