@inflexa-ai/harness 0.41.0 → 0.41.1

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 (58) hide show
  1. package/dist/prompts/briefing.d.ts +28 -1
  2. package/dist/prompts/briefing.js +44 -1
  3. package/dist/prompts/compaction.d.ts +2 -2
  4. package/dist/prompts/compaction.js +9 -5
  5. package/dist/prompts/sandbox-standards.d.ts +1 -1
  6. package/dist/prompts/sandbox-standards.js +4 -0
  7. package/dist/runtime/assemble.d.ts +4 -2
  8. package/dist/runtime/assemble.js +8 -1
  9. package/dist/schemas/file-metadata.js +9 -9
  10. package/dist/schemas/plan-schemas.d.ts +3 -3
  11. package/dist/tools/ad-hoc-router.js +9 -4
  12. package/dist/tools/bio/chembl.js +8 -5
  13. package/dist/tools/bio/comptox.js +4 -4
  14. package/dist/tools/bio/drug-gene-interactions.js +5 -4
  15. package/dist/tools/bio/gene-disease-evidence.js +4 -4
  16. package/dist/tools/bio/lookup-annotation.js +7 -3
  17. package/dist/tools/bio/opentargets.js +8 -6
  18. package/dist/tools/bio/pubchem.js +4 -2
  19. package/dist/tools/bio/pubmed.d.ts +1 -1
  20. package/dist/tools/bio/pubmed.js +10 -6
  21. package/dist/tools/bio/search-clinical-trials.js +2 -1
  22. package/dist/tools/bio/search-gene.js +2 -1
  23. package/dist/tools/bio/search-interactions.js +8 -4
  24. package/dist/tools/bio/target-safety.js +2 -1
  25. package/dist/tools/display/show-user.js +1 -1
  26. package/dist/tools/execute-analysis.js +14 -4
  27. package/dist/tools/lib/opentargets-client.d.ts +8 -3
  28. package/dist/tools/lib/opentargets-client.js +12 -3
  29. package/dist/tools/read-tool-output.js +7 -4
  30. package/dist/tools/report-authoring/authoring-tools.js +22 -15
  31. package/dist/tools/report-session/derive-table.js +5 -1
  32. package/dist/tools/report-session/examine-page.js +5 -4
  33. package/dist/tools/report-session/preview-report.js +6 -1
  34. package/dist/tools/report-session/record-version.js +2 -1
  35. package/dist/tools/research/context7-docs.js +8 -2
  36. package/dist/tools/research/generate-analogy-report.js +15 -15
  37. package/dist/tools/research/generate-plan.js +12 -7
  38. package/dist/tools/research/inspect-data-profile.js +7 -5
  39. package/dist/tools/research/inspect-run.js +6 -2
  40. package/dist/tools/research/literature-reviewer.js +11 -6
  41. package/dist/tools/research/search-arxiv.js +2 -1
  42. package/dist/tools/research/search-github-repos.js +9 -3
  43. package/dist/tools/sandbox/list-available-refs.js +3 -2
  44. package/dist/tools/sandbox/report-blocker.js +6 -1
  45. package/dist/tools/sandbox/skills.js +4 -2
  46. package/dist/tools/start-report-session.js +4 -1
  47. package/dist/tools/workspace/edit-file.js +4 -3
  48. package/dist/tools/workspace/execute-command.js +6 -5
  49. package/dist/tools/workspace/grep.js +9 -7
  50. package/dist/tools/workspace/list-files.js +1 -1
  51. package/dist/tools/workspace/read-file.js +2 -1
  52. package/dist/tools/workspace/scan-inputs.js +3 -1
  53. package/dist/tools/workspace/show-file.js +2 -0
  54. package/dist/tools/workspace/workspace-search.js +3 -1
  55. package/dist/tools/workspace/write-file.js +5 -3
  56. package/dist/workflows/execute-analysis.d.ts +28 -5
  57. package/dist/workflows/execute-analysis.js +34 -10
  58. package/package.json +1 -1
@@ -48,8 +48,10 @@ export function createSkillTools(deps) {
48
48
  id: "skill_search",
49
49
  description: "Search your skills (method decision trees, API references, worked " +
50
50
  "examples) by keyword. Start here when picking a method or verifying an " +
51
- "API detail. Returns matching skill files with a line snippet — then " +
52
- "`skill_read` the file. No matches returns a data variant.",
51
+ "API detail. The query splits on whitespace into words, and each word matches as a " +
52
+ "case-insensitive substring. Returns one entry per matching skill file — its skill, its path, " +
53
+ "and the first line that holds a query word — ranked by how many distinct query words the file " +
54
+ "holds. Then `skill_read` the file. No matches returns a data variant.",
53
55
  inputSchema: z.object({
54
56
  query: z.string().min(1).describe("Keywords to match (e.g. 'PyDESeq2 contrast syntax')."),
55
57
  topK: z.number().int().min(1).max(MAX_TOP_K).optional().describe(`Max results to return. Defaults to ${DEFAULT_TOP_K}.`),
@@ -127,7 +127,10 @@ export function createStartReportSessionTool(deps) {
127
127
  "Keep the whole brief under approximately 2000 tokens. " +
128
128
  "When the conversation holds no user turn of new work after the last report session, the tool " +
129
129
  "starts nothing and it names that session. The ask that started that session is not new work. " +
130
- "Then tell the user to continue in that chat.",
130
+ "Then tell the user to continue in that chat. " +
131
+ "The result `outcome` is `started` or `existing-session` with the `threadId` of the report chat. " +
132
+ "`no_browser` means that this deployment has no browser for the report agent, thus no report session can start; tell the user. " +
133
+ "`refused`, `parent_not_found`, `parent_not_a_conversation`, and `failed` mean that nothing started, and `detail` gives the reason where there is one.",
131
134
  inputSchema: startReportSessionInput,
132
135
  executionMode: "inline",
133
136
  describeCall: "none",
@@ -116,9 +116,10 @@ export function createEditFileTool(deps) {
116
116
  "(default), old_string must occur exactly once. For a bulk pattern " +
117
117
  "edit set regex=true: old_string is a JS regex, new_string may use $1 " +
118
118
  "references, and exactly one of replace_all=true or expected_matches " +
119
- "gates the write. Returns `out_of_prefix` / `out_of_scope` / " +
120
- "`file_not_found` / `not_found` / `not_unique` / `match_count_mismatch` " +
121
- "data variants — never throws on expected outcomes.",
119
+ "gates the write. Returns `out_of_prefix` / `out_of_scope` / `symlink_denied` / " +
120
+ "`file_not_found` / `not_found` / `not_unique` / `match_count_mismatch` / " +
121
+ "`invalid_pattern` / `invalid_arguments` data variants — never throws on expected outcomes. " +
122
+ "A data variant other than `ok` writes nothing.",
122
123
  inputSchema: EditFileInputSchema,
123
124
  // The path only. The replaced text is file content, not a description.
124
125
  describeCall: ({ path }) => path,
@@ -61,11 +61,12 @@ export function createExecuteCommandTool(deps) {
61
61
  `tools don't express. stdout and stderr are each capped at ${EXEC_STREAM_BYTE_CAP / (1024 * 1024)} MiB; the end ` +
62
62
  "of a longer stream is dropped, and the result carries its truncation flag. A long result comes back as an " +
63
63
  "excerpt of its start and its end, with a reference to read the rest when the harness keeps it. " +
64
- "Failures are returned as data, " +
65
- "not thrown. stdout/stderr are EPHEMERAL and are NOT a deliverable — use " +
66
- "this to confirm a command ran, not to produce results. Do NOT compute " +
67
- "analysis results via 'python -c' / inline one-liners and read them from " +
68
- "stdout; that work is lost. Write a script and persist its outputs to output/.",
64
+ "Failures are returned as data, not thrown: `status` is `ok` whenever the call completed, so read " +
65
+ "`exitCode` for the outcome of the command. `timedOut: true` means the timeout stopped the command, and a " +
66
+ "`syntheticFailure` with `exitCode: null` means the sandbox died under the command (its reason names an " +
67
+ "out-of-memory kill). stdout and stderr are not a deliverable: they are ephemeral, " +
68
+ "so use them to confirm that a command ran. A result that you compute with 'python -c' or an inline " +
69
+ "one-liner and read from stdout is lost. Write a script and persist its outputs to output/.",
69
70
  inputSchema: ExecuteCommandInputSchema,
70
71
  // The script is what a reader recognises, and it survives a long
71
72
  // invocation whose flags would otherwise push it past the length cap.
@@ -21,7 +21,8 @@ const GrepInputSchema = z.object({
21
21
  pattern: z
22
22
  .string()
23
23
  .min(1)
24
- .describe("Regular expression to match. Anchored per line; case-sensitive unless " + "`ignoreCase` is true."),
24
+ .describe("JavaScript regular expression, tested against each line separately. It matches anywhere in the line " +
25
+ "unless you add `^` or `$`; case-sensitive unless `ignoreCase` is true."),
25
26
  path: z
26
27
  .string()
27
28
  .min(1)
@@ -31,12 +32,13 @@ const GrepInputSchema = z.object({
31
32
  export function createGrepTool(fs, workingDir) {
32
33
  return defineTool({
33
34
  id: "grep",
34
- description: "Regex search over workspace files (input data, prior runs, step " +
35
- "outputs, summaries, syntheses, data profile). Pass a file or a " +
36
- "directory path; directories are walked recursively. Results are " +
37
- `capped at ${DEFAULT_MAX_MATCHES} matches and ${DEFAULT_MAX_MATCH_BYTES} ` +
38
- "bytes per line — narrow your path or pattern if you hit the cap. " +
39
- "No matches and out-of-scope paths return data variants, not errors.",
35
+ description: "Regex search over the text files of the workspace (input data, prior runs, step " +
36
+ "outputs, summaries, syntheses). Pass a file or a " +
37
+ `directory path; directories are walked recursively, to a depth of ${MAX_DIR_DEPTH}. Results are ` +
38
+ `capped at ${DEFAULT_MAX_MATCHES} matches, and each preview at ${DEFAULT_MAX_MATCH_BYTES} characters of its line. ` +
39
+ `Only the first ${MAX_GREP_FILE_BYTES / (1024 * 1024)} MiB of each file is searched, and a file with a NUL byte ` +
40
+ "in its first KiB is skipped as binary. A `truncated` status means the match cap or the file cap cut the search; " +
41
+ "narrow your path or pattern. No matches, missing paths, and out-of-scope paths return data variants, not errors.",
40
42
  inputSchema: GrepInputSchema,
41
43
  // Both fields, because either alone loses the call: the same pattern over
42
44
  // two trees and two patterns over one tree are both ordinary sequences.
@@ -20,7 +20,7 @@ export function createListFilesTool(fs, workingDir) {
20
20
  return defineTool({
21
21
  id: "list_files",
22
22
  description: "List the entries (files and directories, with file sizes) in a directory of the analysis's " +
23
- "own workspace tree. Faster than `ls` via execute_command. Its scope is that tree and nothing " +
23
+ "own workspace tree. It reads the tree directly and runs no sandbox command. It does not recurse. Its scope is that tree and nothing " +
24
24
  "else: it never reaches a directory outside the analysis, including the host process's current " +
25
25
  "working directory. Missing and out-of-scope paths return a data variant, not an error.",
26
26
  inputSchema: ListFilesInputSchema,
@@ -57,7 +57,8 @@ export function createReadFileTool(fs, workingDir) {
57
57
  "as UTF-8 text. Output is capped at " +
58
58
  `${DEFAULT_MAX_BYTES} bytes; pass headLines or tailLines to read a ` +
59
59
  "specific window of a large file (typical for bio CSV/TSV/log files). " +
60
- "Oversize reads come back truncated with a marker. This tool is for " +
60
+ "An oversize read returns status `truncated` with the bytes that fit (the start of the file, or " +
61
+ "the end for tailLines), `totalSize`, and `returnedBytes`. This tool is for " +
61
62
  "text; a binary file (one holding NUL bytes, e.g. a .gz or image) comes " +
62
63
  "back as a `binary` data variant rather than decoded garbage. Missing " +
63
64
  "paths and paths outside the analysis tree return a data variant — not an error.",
@@ -23,7 +23,9 @@ export function createScanInputsTool(deps) {
23
23
  "the shapes its filenames form (sets of files whose names differ only at marked positions), " +
24
24
  "the distinct values each varying position takes, how those positions co-occur, value overlap " +
25
25
  "between shapes, and the files that share structure with nothing else. " +
26
- "Deterministic — no model, no per-file decoding — so it costs the same on three files as on three thousand. " +
26
+ "It also reads the header of one member file for each shape (magic bytes, a preamble, a header row, or a container index). " +
27
+ "Deterministic — no model — and the manifest it returns is bounded, so it costs the same context on three files as on three thousand. " +
28
+ "The walk stops at a ceiling of files; a manifest marked INCOMPLETE describes part of the subtree, so scan a narrower path. " +
27
29
  "Your briefing already carries a scan of the whole input tree; call this to look at a subtree more closely, " +
28
30
  "or to check a grouping you are unsure about. " +
29
31
  "It reports observations only: which files are one KIND of data, and what a varying position MEANS, are yours to decide.",
@@ -34,6 +34,8 @@ export const showFileTool = defineTool({
34
34
  "inline, or paste the bytes. Pick this tool by what you are referencing, not by how the output looks: " +
35
35
  "NOT for content you synthesized (a chart, a table, a snippet you invented — use `show_user`), NOT for a stored plan (`show_plan`). " +
36
36
  "Paths are analysis-rooted (no leading slash, no `..`); discover them with `workspace_search` or `list_files`. " +
37
+ "The tool checks the shape of each path only, not that the file exists, so a wrong path fails only when the user views the card; " +
38
+ "`shown: false` with `invalid_path` means a path has a leading slash or a `..` segment. " +
37
39
  "Up to 10 files per call — pass a related set in ONE call and they render as a gallery, rather than one call per figure. " +
38
40
  "Cards render in call order, so to interleave figures with prose alternate `show_user(markdown)` and `show_file` calls " +
39
41
  "(markdown image syntax pointing at a workspace file does NOT render — this tool is the only way to show one).",
@@ -53,7 +53,9 @@ export function createWorkspaceSearchTool(pool, embedding) {
53
53
  return defineTool({
54
54
  id: "workspace_search",
55
55
  description: "Semantic search over the analysis workspace. Returns ranked entries with descriptions " +
56
- "and metadata; read a file separately to see its contents. `type` restricts results to one " +
56
+ "and metadata; read a file separately to see its contents. The nearest entries always come back, up to " +
57
+ "`limit`, with no relevance cut: `score` is the cosine similarity to the query, and a low score is a weak match, " +
58
+ "not a hit. An empty `results` list means that nothing of that type is indexed yet. `type` restricts results to one " +
57
59
  'entry type: "input-group" (a GROUP of input data the data profiler declared, carrying its ' +
58
60
  'derived count, its display pattern, and its slots), "input-dimension" (something that VARIES ' +
59
61
  "across the dataset — subject, timepoint, batch — with the observations evidencing it), " +
@@ -25,9 +25,11 @@ export function createWriteFileTool(deps) {
25
25
  // body runs unwrapped in the workflow body (see the harness-tools spec).
26
26
  executionMode: "workflow",
27
27
  description: "Write a UTF-8 text file in your working directory. Relative paths " +
28
- "resolve against it; a path outside it returns an `out_of_prefix` data " +
29
- "variant (no I/O), and one escaping the analysis tree returns " +
30
- "`out_of_scope`.",
28
+ "resolve against it. The write replaces an existing file whole, and it makes " +
29
+ "missing parent directories. On success it returns `status: ok`, the absolute " +
30
+ "'/<analysisId>/...' path, and `bytesWritten`. A path outside the working directory " +
31
+ "returns an `out_of_prefix` data variant (no I/O), one escaping the analysis tree returns " +
32
+ "`out_of_scope`, and a symbolic link on the path returns `symlink_denied`.",
31
33
  inputSchema: WriteFileInputSchema,
32
34
  // The path only. `content` is a whole file and must never ride a display
33
35
  // channel, which the emit-site length cap enforces regardless.
@@ -76,6 +76,8 @@ import type { Logger } from "../lib/logger.js";
76
76
  import type { UsageRecorder } from "../billing/usage-recorder.js";
77
77
  import type { CitationResolver } from "../citations/types.js";
78
78
  import type { SynthesisStatus, UpdateStepExecutionInput } from "../state/index.js";
79
+ import { type StepBriefing } from "../prompts/briefing.js";
80
+ import type { WorkingMemoryStore } from "../memory/working-memory.js";
79
81
  import type { AnalysisStep } from "../schemas/workflow-state.js";
80
82
  import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
81
83
  import type { BioToolKeys } from "../tools/bio/keys.js";
@@ -248,6 +250,11 @@ export interface ExecuteAnalysisDeps {
248
250
  readonly usageRecorder?: UsageRecorder;
249
251
  /** The store of the loops of the run synthesis and of their read tools. */
250
252
  readonly toolOutputStore?: ToolOutputStore;
253
+ /**
254
+ * The working memory of the analysis. The body only reads it: each step seed
255
+ * carries the goal and the constraints as they are at the dispatch of that step.
256
+ */
257
+ readonly workingMemory: WorkingMemoryStore;
251
258
  /**
252
259
  * The provenance seam of the composition. The body reads its run emit member,
253
260
  * and it reads no other member.
@@ -309,11 +316,6 @@ export declare function buildChildInput(args: {
309
316
  * rendered any earlier (at plan submission, say) cannot mention them, and the
310
317
  * agent is left to rediscover its own upstream.
311
318
  *
312
- * Everything read here is durable state written by an already-settled child
313
- * (`output/summary.md`, `cortex_artifacts`) or by the profiler
314
- * (`cortex_analysis_state`) — never live in-flight state, which would not
315
- * reproduce.
316
- *
317
319
  * REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed string is
318
320
  * checkpointed and a replay returns it verbatim — the DB and disk are not read
319
321
  * again and cannot drift the seed under a recovered run. Never call it
@@ -325,6 +327,27 @@ export declare function composeStepSeed(args: {
325
327
  runId: string;
326
328
  deps: ExecuteAnalysisDeps;
327
329
  }): Promise<string>;
330
+ /**
331
+ * Do every read of one step's package and give the data its seed is rendered
332
+ * from. All reads live here, so `composeStepBriefing` stays a pure renderer.
333
+ *
334
+ * Everything read is durable state: written by an already-settled child
335
+ * (`output/summary.md`, `cortex_artifacts`), by the profiler
336
+ * (`cortex_analysis_state`), or by the conversation agent
337
+ * (`cortex_working_memory`). The working memory is the one read that can
338
+ * change while the run is live — it is read per dispatch, not once per run,
339
+ * so an edit during a run reaches each later step, and two steps of one run
340
+ * can carry different copies.
341
+ *
342
+ * A read failure throws (house rules): a swallowed error would be frozen into
343
+ * the checkpointed seed and served to every replay of this step.
344
+ */
345
+ export declare function gatherStepPackage(args: {
346
+ input: ExecuteAnalysisInput;
347
+ stepId: string;
348
+ runId: string;
349
+ deps: ExecuteAnalysisDeps;
350
+ }): Promise<StepBriefing>;
328
351
  /**
329
352
  * Project a classified synthesis outcome onto the run-phase `synthesis` ledger
330
353
  * row's terminal update (see the run-synthesis-outcome spec). The blocker keeps
@@ -82,7 +82,7 @@ import { unwrapOrThrow } from "../lib/result.js";
82
82
  import { countArtifactsForRun, insertStepExecution, loadDataProfileStatus, queryActiveRun, queryStepArtifactPaths, queryStepsByRun, seedStepExecutions, setRunSynthesisOutcome, suspendAnalysis as suspendAnalysisQuery, sweepPendingStepExecutions, updateRunStatus, updateStepExecution, } from "../state/index.js";
83
83
  import { addChatUsage, hasReportedUsage } from "../loop/metrics.js";
84
84
  import { SYNTHESIS_AGENT_ID, loadStepSummariesFromDisk } from "../execution/run-synthesis.js";
85
- import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing } from "../prompts/briefing.js";
85
+ import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing, emptyStepMemory } from "../prompts/briefing.js";
86
86
  import { SYNTHESIS_STEP_ID, runDir, runStepDir, stepSubdir, stepWritePrefix, toSandboxPath } from "../workspace/paths.js";
87
87
  import { isChatDataPart } from "../sandbox/sandbox-step-translate.js";
88
88
  import { synthesizeRun } from "../app/synthesize-run.js";
@@ -220,17 +220,30 @@ export function buildChildInput(args) {
220
220
  * rendered any earlier (at plan submission, say) cannot mention them, and the
221
221
  * agent is left to rediscover its own upstream.
222
222
  *
223
- * Everything read here is durable state written by an already-settled child
224
- * (`output/summary.md`, `cortex_artifacts`) or by the profiler
225
- * (`cortex_analysis_state`) — never live in-flight state, which would not
226
- * reproduce.
227
- *
228
223
  * REPLAY: the caller wraps this in a `DBOS.runStep`, so the composed string is
229
224
  * checkpointed and a replay returns it verbatim — the DB and disk are not read
230
225
  * again and cannot drift the seed under a recovered run. Never call it
231
226
  * unwrapped from the workflow body.
232
227
  */
233
228
  export async function composeStepSeed(args) {
229
+ return composeStepBriefing(await gatherStepPackage(args));
230
+ }
231
+ /**
232
+ * Do every read of one step's package and give the data its seed is rendered
233
+ * from. All reads live here, so `composeStepBriefing` stays a pure renderer.
234
+ *
235
+ * Everything read is durable state: written by an already-settled child
236
+ * (`output/summary.md`, `cortex_artifacts`), by the profiler
237
+ * (`cortex_analysis_state`), or by the conversation agent
238
+ * (`cortex_working_memory`). The working memory is the one read that can
239
+ * change while the run is live — it is read per dispatch, not once per run,
240
+ * so an edit during a run reaches each later step, and two steps of one run
241
+ * can carry different copies.
242
+ *
243
+ * A read failure throws (house rules): a swallowed error would be frozen into
244
+ * the checkpointed seed and served to every replay of this step.
245
+ */
246
+ export async function gatherStepPackage(args) {
234
247
  const { input, stepId, runId, deps } = args;
235
248
  const step = input.planStepById[stepId];
236
249
  if (!step) {
@@ -246,9 +259,19 @@ export async function composeStepSeed(args) {
246
259
  analysisId,
247
260
  workingDir: toSandboxPath(workspaceRoot, analysisId, stepWritePrefix({ workspaceRoot, runId, stepId })),
248
261
  };
249
- // A DB failure here throws (house rules): a swallowed error would be frozen
250
- // into the checkpointed seed and served to every replay of this step.
251
262
  const profile = unwrapOrThrow(await loadDataProfileStatus(deps.pool, analysisId));
263
+ // A failed memory read leaves the section out: the step runs without the copy of the memory, not without a seed.
264
+ const memoryRead = await deps.workingMemory.load(analysisId);
265
+ if (memoryRead.isErr()) {
266
+ const log = (deps.logger ?? createNoopLogger()).named("executeAnalysis.seed");
267
+ log.warn("the working-memory read failed, thus the seed has no memory section", {
268
+ analysisId,
269
+ stepId,
270
+ error: describeDbError(memoryRead.error),
271
+ ...log.errorFields(memoryRead.error.cause),
272
+ });
273
+ }
274
+ const memory = memoryRead.isOk() ? memoryRead.value : emptyStepMemory();
252
275
  const upstream = await collectUpstreamHandoffs({
253
276
  analysisId,
254
277
  runId,
@@ -257,12 +280,13 @@ export async function composeStepSeed(args) {
257
280
  agentByStepId: input.agentByStepId,
258
281
  pool: deps.pool,
259
282
  });
260
- return composeStepBriefing({
283
+ return {
261
284
  step,
262
285
  workspace,
263
286
  profile: profile?.result ?? null,
264
287
  upstream,
265
- });
288
+ memory: { goal: memory.goal, constraints: memory.constraints },
289
+ };
266
290
  }
267
291
  /**
268
292
  * Gather what each completed dependency produced. Dependency order is the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inflexa-ai/harness",
3
- "version": "0.41.0",
3
+ "version": "0.41.1",
4
4
  "description": "Host-agnostic bioinformatics agent harness — hand-rolled agent loop, DBOS-durable workflows, sandboxed compute.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",