@inflexa-ai/harness 0.48.1 → 0.48.2

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 (107) hide show
  1. package/dist/contracts/chat-events.d.ts +40 -0
  2. package/dist/contracts/chat-frame.js +11 -0
  3. package/dist/contracts/index.d.ts +1 -1
  4. package/dist/contracts/message.d.ts +7 -1
  5. package/dist/contracts/schemas/chat-events.d.ts +319 -0
  6. package/dist/contracts/schemas/chat-events.js +29 -0
  7. package/dist/execution/run-synthesis.js +1 -0
  8. package/dist/index.d.ts +3 -1
  9. package/dist/index.js +1 -0
  10. package/dist/input-sanitization.d.ts +30 -2
  11. package/dist/input-sanitization.js +187 -2
  12. package/dist/loop/run-loop.js +50 -5
  13. package/dist/loop/tool-adapter.d.ts +8 -0
  14. package/dist/loop/tool-adapter.js +75 -4
  15. package/dist/loop/tool-detail.d.ts +5 -1
  16. package/dist/loop/tool-detail.js +7 -3
  17. package/dist/loop/tool-value.d.ts +57 -0
  18. package/dist/loop/tool-value.js +249 -0
  19. package/dist/loop/types.d.ts +9 -2
  20. package/dist/memory/conversation-display-storage.js +4 -1
  21. package/dist/memory/count-tokens.js +29 -2
  22. package/dist/sandbox/sandbox-step-translate.d.ts +5 -9
  23. package/dist/sandbox/sandbox-step-translate.js +5 -9
  24. package/dist/tasks/data-profile-activity.d.ts +3 -7
  25. package/dist/tasks/data-profile-activity.js +1 -1
  26. package/dist/tasks/data-profile.js +3 -6
  27. package/dist/tools/ad-hoc-router.js +1 -0
  28. package/dist/tools/bio/alphafold-prediction.js +1 -0
  29. package/dist/tools/bio/chembl.js +1 -0
  30. package/dist/tools/bio/comptox.js +1 -0
  31. package/dist/tools/bio/drug-gene-interactions.js +1 -0
  32. package/dist/tools/bio/gene-disease-evidence.js +1 -0
  33. package/dist/tools/bio/gene-preclinical-profile.js +1 -0
  34. package/dist/tools/bio/lookup-annotation.js +1 -0
  35. package/dist/tools/bio/opentargets.js +1 -0
  36. package/dist/tools/bio/pubchem.js +1 -0
  37. package/dist/tools/bio/pubmed.js +1 -0
  38. package/dist/tools/bio/search-clinical-trials.js +1 -0
  39. package/dist/tools/bio/search-faers.js +1 -0
  40. package/dist/tools/bio/search-gene.js +1 -0
  41. package/dist/tools/bio/search-geo-datasets.js +1 -0
  42. package/dist/tools/bio/search-interactions.js +1 -0
  43. package/dist/tools/bio/search-patents.js +1 -0
  44. package/dist/tools/bio/search-protein.js +1 -0
  45. package/dist/tools/bio/search-regulatory-approvals.js +1 -0
  46. package/dist/tools/bio/target-safety.js +1 -0
  47. package/dist/tools/capability-data.js +5 -0
  48. package/dist/tools/define-tool.d.ts +18 -0
  49. package/dist/tools/define-tool.js +2 -0
  50. package/dist/tools/display/show-user.js +1 -0
  51. package/dist/tools/execute-analysis.js +4 -0
  52. package/dist/tools/knowledge/check.d.ts +47 -1
  53. package/dist/tools/knowledge/check.js +10 -1
  54. package/dist/tools/knowledge/client.d.ts +1 -1
  55. package/dist/tools/knowledge/recommend.js +4 -3
  56. package/dist/tools/knowledge/situation.d.ts +39 -1
  57. package/dist/tools/knowledge/situation.js +80 -8
  58. package/dist/tools/knowledge/skeleton.d.ts +7 -0
  59. package/dist/tools/knowledge/skeleton.js +88 -2
  60. package/dist/tools/knowledge/template.js +1 -0
  61. package/dist/tools/memory/update-working-memory.js +5 -0
  62. package/dist/tools/read-tool-output.js +4 -0
  63. package/dist/tools/report-authoring/authoring-tools.js +8 -0
  64. package/dist/tools/report-session/examine-page.js +5 -0
  65. package/dist/tools/report-session/list-artifacts.js +5 -0
  66. package/dist/tools/report-session/preview-report.js +1 -0
  67. package/dist/tools/report-session/record-version.js +1 -0
  68. package/dist/tools/report-session/script-tools.js +2 -0
  69. package/dist/tools/research/context7-docs.js +2 -0
  70. package/dist/tools/research/generate-analogy-report.js +3 -0
  71. package/dist/tools/research/generate-plan.js +6 -0
  72. package/dist/tools/research/inspect-data-profile.js +5 -0
  73. package/dist/tools/research/inspect-run.js +5 -0
  74. package/dist/tools/research/resolve-citation.js +1 -0
  75. package/dist/tools/research/search-arxiv.js +1 -0
  76. package/dist/tools/research/search-github-repos.js +1 -0
  77. package/dist/tools/research/search-semantic-scholar.js +1 -0
  78. package/dist/tools/research/web-fetch.js +1 -0
  79. package/dist/tools/research/web-search.js +1 -0
  80. package/dist/tools/sandbox/lease-tools.js +2 -0
  81. package/dist/tools/sandbox/link-packages.js +1 -0
  82. package/dist/tools/sandbox/list-available-packages.js +5 -0
  83. package/dist/tools/sandbox/list-available-refs.js +5 -0
  84. package/dist/tools/sandbox/report-blocker.js +1 -0
  85. package/dist/tools/sandbox/skills.js +2 -0
  86. package/dist/tools/sandbox/submit-file-metadata.js +1 -0
  87. package/dist/tools/script/file-tools.js +6 -0
  88. package/dist/tools/script/run-script.js +20 -1
  89. package/dist/tools/start-report-session.js +1 -0
  90. package/dist/tools/tool-view.d.ts +63 -0
  91. package/dist/tools/tool-view.js +68 -0
  92. package/dist/tools/workspace/edit-file.js +5 -0
  93. package/dist/tools/workspace/execute-command.js +5 -2
  94. package/dist/tools/workspace/file-stat.js +5 -0
  95. package/dist/tools/workspace/grep.js +4 -0
  96. package/dist/tools/workspace/list-files.js +5 -0
  97. package/dist/tools/workspace/read-file.js +4 -0
  98. package/dist/tools/workspace/scan-inputs.js +5 -0
  99. package/dist/tools/workspace/show-file.js +1 -0
  100. package/dist/tools/workspace/show-plan.js +1 -0
  101. package/dist/tools/workspace/workspace-search.js +5 -0
  102. package/dist/tools/workspace/write-file.js +7 -2
  103. package/dist/workflows/execute-analysis.js +2 -1
  104. package/dist/workflows/sandbox-step.js +3 -7
  105. package/package.json +1 -1
  106. package/dist/tools/detail-resolver.d.ts +0 -40
  107. package/dist/tools/detail-resolver.js +0 -44
@@ -19,6 +19,13 @@
19
19
  * one enrichment step on its own track, and one report step. A group with
20
20
  * no step in the procedure is absent from the skeleton.
21
21
  *
22
+ * A step whose own template reads the output of another template gets its
23
+ * own group, because a fold keeps the template of the central step alone and
24
+ * it would drop the others. Thus the four steps of the dose-response chain
25
+ * (the benchmark dose of each gene, the benchmark dose of each gene set, the
26
+ * point of departure, and the exposure margin) are four groups of one track,
27
+ * in that order, and each of the other four new steps is a group of its own.
28
+ *
22
29
  * The central step of a group names the method of the skeleton step, and it
23
30
  * gives the alternatives, the template, and the environment: an alternative
24
31
  * of a folded step (a normalization in the differential expression group)
@@ -40,6 +47,21 @@ const GROUPS = [
40
47
  steps: ["filter_low_counts", "normalize", "model_design", "differential_expression", "shrink_lfc", "multiple_testing"],
41
48
  name: "Differential expression",
42
49
  },
50
+ // The dose-response chain: each step reads the table that the step before
51
+ // it writes, thus the four are one track in the order of the chain. The
52
+ // track is its own, because the chain is a branch off the test and not a
53
+ // part of the core walk, and it ends in a regulatory quantity.
54
+ { id: "T4S1", track: "T4", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["dose_response_fit"], name: "Benchmark dose of each gene" },
55
+ {
56
+ id: "T4S2",
57
+ track: "T4",
58
+ step_type: "analysis",
59
+ agent: "bulk-transcriptomics-agent",
60
+ steps: ["benchmark_dose_sets"],
61
+ name: "Benchmark dose of each gene set",
62
+ },
63
+ { id: "T4S3", track: "T4", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["point_of_departure"], name: "Point of departure" },
64
+ { id: "T4S4", track: "T4", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["exposure_margin"], name: "Exposure margin" },
43
65
  { id: "T2S1", track: "T2", step_type: "enrichment", agent: "enrichment-agent", steps: ["enrichment"], name: "Gene set enrichment" },
44
66
  {
45
67
  id: "T2S2",
@@ -49,12 +71,45 @@ const GROUPS = [
49
71
  steps: ["tf_activity", "pathway_activity"],
50
72
  name: "Regulator and pathway activity",
51
73
  },
74
+ // The connectivity reads the contrast of the study against a reference
75
+ // resource, which is the shape and the agent of this track. The resource
76
+ // is a compendium of contrasts and not a gene set collection, thus the
77
+ // step is a group of its own beside the enrichment and the activity.
78
+ {
79
+ id: "T2S3",
80
+ track: "T2",
81
+ step_type: "analysis",
82
+ agent: "enrichment-agent",
83
+ steps: ["compendium_connectivity"],
84
+ name: "Compendium connectivity",
85
+ },
52
86
  { id: "T1S4", track: "T1", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["variance_partition"], name: "Variance partition" },
53
87
  { id: "T3S1", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["signature_scoring"], name: "Signature scoring" },
54
88
  { id: "T3S2", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["deconvolution"], name: "Cell type deconvolution" },
55
89
  { id: "T3S3", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["coexpression"], name: "Co-expression modules" },
56
90
  { id: "T3S4", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["clustering"], name: "Sample clustering" },
57
91
  { id: "T3S5", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["survival"], name: "Outcome association" },
92
+ // Three computations over the value matrix and the sample table, each
93
+ // independent of the others and of the chain, thus each is a group of
94
+ // this track: a class per gene over the time points, a score per sample
95
+ // against a reference module set, and a call per sample.
96
+ {
97
+ id: "T3S6",
98
+ track: "T3",
99
+ step_type: "analysis",
100
+ agent: "bulk-transcriptomics-agent",
101
+ steps: ["trajectory_classification"],
102
+ name: "Trajectory classification",
103
+ },
104
+ {
105
+ id: "T3S7",
106
+ track: "T3",
107
+ step_type: "analysis",
108
+ agent: "bulk-transcriptomics-agent",
109
+ steps: ["reference_module_score"],
110
+ name: "Reference module score",
111
+ },
112
+ { id: "T3S8", track: "T3", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["classifier_call"], name: "Classifier call" },
58
113
  { id: "T1S5", track: "T1", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["transcript_level"], name: "Transcript-level analysis" },
59
114
  { id: "T1S6", track: "T1", step_type: "analysis", agent: "bulk-transcriptomics-agent", steps: ["annotation"], name: "Identifier annotation" },
60
115
  { id: "T1S3", track: "T1", step_type: "report", agent: "bulk-transcriptomics-agent", steps: ["report"], name: "Report" },
@@ -63,23 +118,54 @@ const GROUPS = [
63
118
  * The dependencies of each group. The QC depends on the cohort assembly when
64
119
  * one is present, a group depends on the QC, on the differential expression
65
120
  * when its input is a results table, and the report depends on every group
66
- * that is present.
121
+ * that is present. A step of the dose-response chain depends on the step
122
+ * whose table it reads: the fit reads the genes that passed the test, the
123
+ * gene sets read the gene table of the fit, the point of departure reads the
124
+ * set table and the gene table, and the margin reads the point of departure.
67
125
  */
68
126
  const DEPENDS = {
69
127
  T0S1: [],
70
128
  T1S1: ["T0S1"],
71
129
  T1S2: ["T1S1"],
130
+ T4S1: ["T1S2"],
131
+ T4S2: ["T4S1"],
132
+ T4S3: ["T4S2", "T4S1"],
133
+ T4S4: ["T4S3"],
72
134
  T2S1: ["T1S2"],
73
135
  T2S2: ["T1S2", "T1S1"],
136
+ T2S3: ["T1S2"],
74
137
  T1S4: ["T1S1"],
75
138
  T3S1: ["T1S1"],
76
139
  T3S2: ["T1S1"],
77
140
  T3S3: ["T1S1"],
78
141
  T3S4: ["T1S1"],
79
142
  T3S5: ["T3S1", "T1S1"],
143
+ T3S6: ["T1S1"],
144
+ T3S7: ["T1S1"],
145
+ T3S8: ["T1S1"],
80
146
  T1S5: ["T1S1"],
81
147
  T1S6: ["T1S1"],
82
- T1S3: ["T1S2", "T2S1", "T2S2", "T1S4", "T3S1", "T3S2", "T3S3", "T3S4", "T3S5", "T1S5", "T1S6"],
148
+ T1S3: [
149
+ "T1S2",
150
+ "T4S1",
151
+ "T4S2",
152
+ "T4S3",
153
+ "T4S4",
154
+ "T2S1",
155
+ "T2S2",
156
+ "T2S3",
157
+ "T1S4",
158
+ "T3S1",
159
+ "T3S2",
160
+ "T3S3",
161
+ "T3S4",
162
+ "T3S5",
163
+ "T3S6",
164
+ "T3S7",
165
+ "T3S8",
166
+ "T1S5",
167
+ "T1S6",
168
+ ],
83
169
  };
84
170
  /** A parameter value as a setting: a scalar stays, a list becomes a list of strings, anything else its text. */
85
171
  function settingValue(value) {
@@ -124,6 +124,7 @@ export function createKnowledgeTemplateTool(deps) {
124
124
  "A change the slots do not cover: use `edit_file` on a line marked `# [adaptable: ...]` in the rendered script, and keep the edit small; the record lists each such edit. " +
125
125
  "`match: unavailable` means the service did not answer, and `match: snapshot_mismatch` means the service moved to another release than the plan pinned; in both cases write the script yourself as you would without this tool, and state it in your summary.",
126
126
  inputSchema: KnowledgeTemplateInputSchema,
127
+ view: "none",
127
128
  describeCall: ({ template }) => template,
128
129
  describeResult: (_input, result) => "status" in result ? (result.status === "ok" ? `${result.script_path} (${result.environment_match})` : result.status) : result.match,
129
130
  execute: async ({ template, slots, overrides, script_name }, ctx) => {
@@ -165,6 +165,11 @@ export function createUpdateWorkingMemoryTool(workingMemory, pool, opts = DEFAUL
165
165
  "user's last message, run contents (tables, gene lists, file paths), or anything you can retrieve " +
166
166
  "again with inspect_run, read_file, or search.",
167
167
  inputSchema,
168
+ view: {
169
+ input: (input) => input,
170
+ result: (_input, result) => result,
171
+ error: true,
172
+ },
168
173
  // Four writes in one turn are four identical chips without this. The
169
174
  // entry id rides along on revise/retire because it is the only thing
170
175
  // separating two retirements from the same section.
@@ -122,6 +122,10 @@ export function createReadToolOutputTool(store) {
122
122
  `${MAX_MATCHES} matches, each with its offset and the text around it. When a result gives \`more: true\`, ` +
123
123
  "call again with `offset` set to its `end`.",
124
124
  inputSchema: ReadToolOutputInputSchema,
125
+ view: {
126
+ input: (input) => input,
127
+ error: true,
128
+ },
125
129
  // The reference alone repeats across the pages of one text, and the pattern is what tells two searches apart.
126
130
  describeCall: ({ ref, pattern }) => (pattern === undefined ? ref : `${pattern} in ${ref}`),
127
131
  execute: async ({ ref, offset = 0, limit, pattern }, ctx) => {
@@ -524,6 +524,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
524
524
  "A reference names the path of a pinned artifact, and the session stamps the hash from the pinned evidence.",
525
525
  inputSchema: addBlockInput,
526
526
  outputSchema: MutationOutputSchema,
527
+ view: "none",
527
528
  describeCall: (input) => {
528
529
  const block = decodeBlockPayload(input.block);
529
530
  const kind = readKind(block);
@@ -559,6 +560,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
559
560
  "A reference names the path of a pinned artifact, and the session stamps the hash from the pinned evidence.",
560
561
  inputSchema: changeBlockInput,
561
562
  outputSchema: MutationOutputSchema,
563
+ view: "none",
562
564
  describeCall: (input) => `change ${input.targetId}`,
563
565
  execute: async (input, ctx) => {
564
566
  const opening = await openMutation(ctx);
@@ -582,6 +584,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
582
584
  description: "Remove one block by its id. A removed section takes its whole subtree with it, and the id is free for a later add.",
583
585
  inputSchema: removeBlockInput,
584
586
  outputSchema: MutationOutputSchema,
587
+ view: "none",
585
588
  describeCall: (input) => `remove ${input.targetId}`,
586
589
  execute: async (input, ctx) => {
587
590
  const opening = await openMutation(ctx);
@@ -602,6 +605,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
602
605
  "A section cannot move into its own subtree.",
603
606
  inputSchema: moveBlockInput,
604
607
  outputSchema: MutationOutputSchema,
608
+ view: "none",
605
609
  describeCall: (input) => `move ${input.targetId}`,
606
610
  execute: async (input, ctx) => {
607
611
  const opening = await openMutation(ctx);
@@ -622,6 +626,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
622
626
  description: "Set the title of the whole report, not the title of a section. A draft starts with no title, and a document needs one.",
623
627
  inputSchema: setTitleInput,
624
628
  outputSchema: MutationOutputSchema,
629
+ view: "none",
625
630
  describeCall: (input) => `title the report ${input.title}`,
626
631
  // The title sits on the document, thus no container changes its child order.
627
632
  execute: async (input, ctx) => {
@@ -648,6 +653,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
648
653
  "and read one block when the label is not enough.",
649
654
  inputSchema: readOutlineInput,
650
655
  outputSchema: OutlineOutputSchema,
656
+ view: "none",
651
657
  // The input carries no field, thus a hook can only restate the name of the tool.
652
658
  describeCall: "none",
653
659
  execute: async (_input, ctx) => {
@@ -665,6 +671,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
665
671
  "because the outline already names every block under it. Use it when the outline label is not enough.",
666
672
  inputSchema: readBlockInput,
667
673
  outputSchema: ReadBlockOutputSchema,
674
+ view: "none",
668
675
  describeCall: (input) => `read ${input.targetId}`,
669
676
  execute: async (input, ctx) => {
670
677
  const opened = await openThread(ctx);
@@ -685,6 +692,7 @@ export function createReportAuthoringTools(gateway, deps = {}) {
685
692
  "preview_report and record_report_version run the same check before they act, thus a finish directly before one of them does that check a second time. " +
686
693
  "Use it to make sure that the draft passes the whole document schema, the id rule, and the structural tier.",
687
694
  inputSchema: finishDraftInput,
695
+ view: "none",
688
696
  // The input carries no field, thus a hook can only restate the name of the tool.
689
697
  describeCall: "none",
690
698
  execute: async (_input, ctx) => {
@@ -254,6 +254,11 @@ export function createExaminePageTool(deps) {
254
254
  "The outcomes no-page and missed-stamp mean that no preview rendered the current page: run preview_report, then look again. " +
255
255
  "The outcome no-browser means that this host gives no browser, and a repeat gives the same outcome.",
256
256
  inputSchema: examinePageInput,
257
+ view: {
258
+ input: (input) => input,
259
+ result: (_input, result) => result,
260
+ error: true,
261
+ },
257
262
  describeCall: (input) => {
258
263
  const blockId = input.blockId ?? undefined;
259
264
  return blockId === undefined ? "look at the page" : `look at ${blockId}`;
@@ -148,6 +148,11 @@ export function createListPinnedArtifactsTool(deps) {
148
148
  "Read the listing to orient before you bind a block. Read the columns of one artifact to choose the column that a locator names. " +
149
149
  "A reference names the path alone: the session stamps the hash from this evidence, thus the tool gives no hash.",
150
150
  inputSchema: listPinnedArtifactsInput,
151
+ view: {
152
+ input: (input) => input,
153
+ result: (_input, result) => result,
154
+ error: true,
155
+ },
151
156
  describeCall: (input) => input.path ?? "the pinned set",
152
157
  // The size of the listing is what a watcher reads, and the cap makes that size differ from the
153
158
  // pinned set. Thus a truncated listing names the total beside the count, and a whole listing names
@@ -376,6 +376,7 @@ export function createPreviewReportTool(deps) {
376
376
  "The render marks the page that examine_page looks at next. " +
377
377
  "Use it to see the report, and to confirm that each reference resolves.",
378
378
  inputSchema: previewReportInput,
379
+ view: "none",
379
380
  describeCall: "none",
380
381
  // The page path is the one fact of a preview that a watcher wants, and it exists only in the
381
382
  // result. Each other arm is a degraded condition whose kind already names it, thus the kind
@@ -138,6 +138,7 @@ export function createRecordVersionTool(deps) {
138
138
  "A thread holds one version, thus a later record replaces it whole. Amend the draft, look at the page again, and record again. " +
139
139
  "After the version lands, the tool removes the file of each derived table that no block of the recorded report binds.",
140
140
  inputSchema: recordVersionInput,
141
+ view: "none",
141
142
  describeCall: "none",
142
143
  // The version id is the one durable product of the first record, thus the created arm names it. A
143
144
  // later record writes over that same id, thus the line names the update and never reads as a
@@ -40,6 +40,7 @@ function createReadTableTool(deps) {
40
40
  "A larger table does not derive; tell the user which table is absent.",
41
41
  inputSchema: readTableInput,
42
42
  outputSchema: ReadTableOutputSchema,
43
+ view: "none",
43
44
  describeCall: ({ path }) => path,
44
45
  describeResult: (_input, result) => `${String(result.rowCount)} rows`,
45
46
  execute: async ({ path, columns }, ctx) => {
@@ -100,6 +101,7 @@ function createWriteDerivedTool(deps) {
100
101
  "Name each pinned table that the script read in `inputs`. A name that this session already holds is refused, thus a second derivation of one table takes a new name.",
101
102
  inputSchema: writeDerivedInput,
102
103
  outputSchema: DerivedOutputSchema,
104
+ view: "none",
103
105
  describeCall: ({ name }) => name,
104
106
  describeResult: (_input, result) => result.path,
105
107
  execute: async ({ name, rows, inputs }, ctx) => {
@@ -51,6 +51,7 @@ export const resolveLibraryIdTool = defineTool({
51
51
  libraryName: z.string().describe("Package or library name to search for (e.g., 'scanpy', 'pysam', 'lifelines')."),
52
52
  query: z.string().describe("What you need help with — used to rank results by relevance (e.g., 'how to run differential expression')."),
53
53
  }),
54
+ view: "none",
54
55
  describeCall: "none",
55
56
  execute: async ({ libraryName, query }) => {
56
57
  const result = await apiFetchValidated(`${CONTEXT7_BASE}/search?query=${encodeURIComponent(libraryName)}&topic=${encodeURIComponent(query)}`, Context7SearchResponseSchema, { maxRetries: 1 });
@@ -80,6 +81,7 @@ export const queryDocsTool = defineTool({
80
81
  libraryId: z.string().describe("Context7 library ID exactly as `resolve_library_id` gave it — an owner and a library, for example '/scverse/scanpy'."),
81
82
  query: z.string().describe("Specific question about the library (e.g., 'rank_genes_groups function parameters and usage')."),
82
83
  }),
84
+ view: "none",
83
85
  describeCall: "none",
84
86
  execute: async ({ libraryId, query }) => {
85
87
  // The library id is the path of the route, not a query parameter. Each
@@ -47,6 +47,7 @@ function createRecordProblemTool() {
47
47
  description: "Record the extraction of phase 1: the problem summary, objects, relations, key terms, and the analogies " +
48
48
  "that phase 2 searches. A later call replaces the record.",
49
49
  inputSchema: RecordProblemInputSchema,
50
+ view: "none",
50
51
  describeCall: "none",
51
52
  execute: () => Promise.resolve(ok({ recorded: true })),
52
53
  });
@@ -57,6 +58,7 @@ function createRecordAnalogyTool() {
57
58
  description: "Record one analogy with its coverage and its cited solutions, when its search is done. A later call " +
58
59
  "with the same analogyTitle replaces the record.",
59
60
  inputSchema: AnalogySchema,
61
+ view: "none",
60
62
  describeCall: ({ analogyTitle }) => analogyTitle,
61
63
  execute: () => Promise.resolve(ok({ recorded: true })),
62
64
  });
@@ -268,6 +270,7 @@ export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
268
270
  "A retry with the same or a similar problem statement meets the same " +
269
271
  "failure, thus surface the message and ask the user to narrow the problem.",
270
272
  inputSchema: generateAnalogyReportInputSchema,
273
+ view: "none",
271
274
  describeCall: "none",
272
275
  startsWorkflow: true,
273
276
  execute: async (input, ctx) => ok(await research(input, ctx)),
@@ -602,6 +602,7 @@ function buildInnerTools(logger, memory) {
602
602
  // structured issue reporting.
603
603
  plan: z.union([PlannerPlanSchema, z.unknown()]).describe("Candidate plan; malformed values are reported in issues."),
604
604
  }),
605
+ view: "none",
605
606
  describeCall: "none",
606
607
  execute: (input) => {
607
608
  // The host owns the grounding fields: it fills them from the last answer of the run.
@@ -639,6 +640,7 @@ function buildInnerTools(logger, memory) {
639
640
  question: terminalTextSchema("One short question that names each missing fact, and the step that needs it."),
640
641
  questionContext: z.string().optional().describe("Optional context: why the plan needs the fact, and what the inputs already say about it."),
641
642
  }),
643
+ view: "none",
642
644
  describeCall: "none",
643
645
  execute: (input) => Promise.resolve(ok({
644
646
  kind: "clarification",
@@ -1181,6 +1183,10 @@ export function createGeneratePlanTool(deps, registerSubAgent) {
1181
1183
  "when the planner is missing a fact it cannot infer; or `error` with {error} when no plan was saved (the planner reported " +
1182
1184
  "a blocker, the save failed, the call timed out or was cancelled, or parentPlanId is not a plan of this analysis).",
1183
1185
  inputSchema: generatePlanInputSchema,
1186
+ view: {
1187
+ input: (input) => input,
1188
+ error: true,
1189
+ },
1184
1190
  describeCall: ({ researchQuestion }) => researchQuestion,
1185
1191
  startsWorkflow: true,
1186
1192
  execute: async (input, ctx) => {
@@ -136,6 +136,11 @@ export function createInspectDataProfileTool(pool) {
136
136
  "against when the input set last changed before you conclude anything: if the data moved since, say the profile " +
137
137
  "needs re-running rather than diagnosing the data itself.",
138
138
  inputSchema: InspectDataProfileInputSchema,
139
+ view: {
140
+ input: (input) => input,
141
+ result: (_input, result) => result,
142
+ error: true,
143
+ },
139
144
  // `execute` defaults both fields internally, and the hook applies the same
140
145
  // defaults. Without them the ordinary call, which names no field at all,
141
146
  // gives no detail. `page` acts only on the `files` scope, as in `execute`.
@@ -109,6 +109,11 @@ export function createInspectRunTool(pool, clock = systemClock) {
109
109
  "For a user-directed check, waitForTerminalSeconds can wait once for 1-30 seconds; if the cutoff returns in_progress, stop polling for that turn and tell the user that the run still runs. " +
110
110
  "Default response is lightweight — pass includeDiagnostics:true to add error/duration/retry telemetry.",
111
111
  inputSchema,
112
+ view: {
113
+ input: (input) => input,
114
+ result: (_input, result) => result,
115
+ error: true,
116
+ },
112
117
  // The two calls do different work: one inspects a named run, the other
113
118
  // pages the list. Naming the run id is what separates repeated polls of
114
119
  // the same run from polls of different ones.
@@ -14,6 +14,7 @@ export function createResolveCitationTool(resolver) {
14
14
  "not_found, unverifiable, or inconclusive. Source failure never becomes not_found — an unreachable authority reports unavailable, and " +
15
15
  "not_found means the authorities that answered hold no such record.",
16
16
  inputSchema: CitationInputSchema,
17
+ view: "none",
17
18
  describeCall: ({ citation }) => `verify ${citation}`,
18
19
  execute: async (input, ctx) => ok(await resolver.resolveOne(input, { signal: ctx.signal })),
19
20
  });
@@ -39,6 +39,7 @@ export const searchArxivTool = defineTool({
39
39
  "and that filter is combined with the query using AND."),
40
40
  limit: z.number().int().min(1).max(20).default(10).describe("Maximum results (1–20, default 10)."),
41
41
  }),
42
+ view: "none",
42
43
  describeCall: "none",
43
44
  execute: async ({ query, categories, limit }, context) => {
44
45
  const result = await source.search({ query, categories, limit }, context.signal);
@@ -87,6 +87,7 @@ export function createSearchGithubReposTool(deps) {
87
87
  "lands here). Report either one and continue, do not retry unchanged.",
88
88
  outputSchema: SearchGithubReposOutputSchema,
89
89
  inputSchema: SearchGithubReposInputSchema,
90
+ view: "none",
90
91
  describeCall: "none",
91
92
  execute: async ({ query, language, limit }) => {
92
93
  const q = language ? `${query} language:${language}` : query;
@@ -46,6 +46,7 @@ export function createSearchSemanticScholarTool(options = {}) {
46
46
  '"reinforcement learning for control" or "feedback stabilisation in oscillators".'),
47
47
  limit: z.number().int().min(1).max(20).default(10).describe("Maximum number of results to return (1–20, default 10)."),
48
48
  }),
49
+ view: "none",
49
50
  describeCall: "none",
50
51
  execute: async ({ query, limit }, context) => {
51
52
  const result = await source.search(query, limit, context.signal);
@@ -614,6 +614,7 @@ export function createWebFetchTool(deps = {}) {
614
614
  "private, link-local, or reserved), http_error, redirect_limit, body_too_large, unextractable, and request_failed each name what " +
615
615
  "happened. Report the status and continue, do not retry unchanged.",
616
616
  inputSchema: WebFetchInputSchema,
617
+ view: "none",
617
618
  describeCall: ({ url }) => url,
618
619
  execute: async ({ url }, ctx) => ok(await fetchOneUrl(url, deps, ctx)),
619
620
  });
@@ -77,6 +77,7 @@ export function createWebSearchTool(deps) {
77
77
  "ABSENCE IS NORMAL: an empty results array means no page matched, and a status of http_error, retryable_status, " +
78
78
  "schema_mismatch, or not_json names a search that did not answer. Report either one and continue, do not retry unchanged.",
79
79
  inputSchema: WebSearchInputSchema,
80
+ view: "none",
80
81
  describeCall: "none",
81
82
  execute: async ({ query, queries, numResults, recencyFilter, domainFilter }) => {
82
83
  const filter = parseDomainFilter(domainFilter);
@@ -24,6 +24,7 @@ export function createAcquireSandboxTool(lease) {
24
24
  "size replaces your sandbox: the files of your working directory stay, and the next command runs on the new " +
25
25
  "size. Your files and your outputs live in the workspace, not in the sandbox.",
26
26
  inputSchema: AcquireSandboxInputSchema,
27
+ view: "none",
27
28
  describeCall: ({ cpu, memoryGb }) => `Waiting for a sandbox (${cpu} CPU, ${memoryGb} GB)`,
28
29
  execute: async ({ cpu, memoryGb, gpuCount }) => {
29
30
  const size = gpuCount === undefined || gpuCount === 0 ? { cpu, memoryGb } : { cpu, memoryGb, gpu: { count: gpuCount } };
@@ -51,6 +52,7 @@ export function createReleaseSandboxTool(lease) {
51
52
  description: "Release your sandbox when your commands are done and you work only with files or research. The files stay. " +
52
53
  "A later `execute_command` needs `acquire_sandbox` again.",
53
54
  inputSchema: ReleaseSandboxInputSchema,
55
+ view: "none",
54
56
  describeCall: () => "Releasing the sandbox",
55
57
  execute: async () => {
56
58
  await lease.release();
@@ -70,6 +70,7 @@ export function createLinkPackagesTool(deps) {
70
70
  "After a `collision` of one name that BOTH tracks hold, call this tool again for that package with the prefixed form, `python:<name>` or `r:<name>`. " +
71
71
  "A collision is terminal only after that second call also refuses, or when it names two versions of one distribution — then report it and continue without the package.",
72
72
  inputSchema: LinkPackagesInputSchema,
73
+ view: "none",
73
74
  describeCall: ({ packages }) => packages.join(", "),
74
75
  execute: async (input) => {
75
76
  // Every entry parses before any link lands. A partial link over a
@@ -495,6 +495,11 @@ export function createListAvailablePackagesTool(deps = {}) {
495
495
  "`limit`: cap the packages listed; the response always carries the true `total` and a `hasMore` flag, so truncation is never silent. " +
496
496
  "Each entry renders as `name==version` where the source pins a version, and a `names` check also reports the store directory and the content hash where the source records them.",
497
497
  inputSchema: ListAvailablePackagesInputSchema,
498
+ view: {
499
+ input: (input) => input,
500
+ result: (_input, result) => result,
501
+ error: true,
502
+ },
498
503
  // `queryPackages` returns on `names` before it reads another field, thus
499
504
  // the presence check comes first. An empty array is not a presence check.
500
505
  // `names` are exact identifiers a caller checks for, thus they name the
@@ -600,6 +600,11 @@ export function createListAvailableRefsTool(deps) {
600
600
  "An empty result means the dataset is absent right now — not that it is unobtainable. Say so; never guess a path, and never silently substitute a different dataset. " +
601
601
  "The store is provisioned by the host and is NOT frozen: datasets can be added through the host's own provisioning path between runs, and whatever is added shows up in a later run. Within one run the listing does not change, thus one call per question is enough.",
602
602
  inputSchema: ListAvailableRefsInputSchema,
603
+ view: {
604
+ input: (input) => input,
605
+ result: (_input, result) => result,
606
+ error: true,
607
+ },
603
608
  // `execute` resolves `path ?? category` for the subtree it inspects, and
604
609
  // it ignores `category` when `path` is present. `query` is an ADDITIVE
605
610
  // filter over that subtree, thus the target keeps naming the call and the
@@ -37,6 +37,7 @@ export function createReportBlockerToolFor(deps) {
37
37
  id: REPORT_BLOCKER_TOOL_ID,
38
38
  description: `Terminal. ${deps.blockedWhen} Pass a clear reason. Stop immediately after calling — take no further actions.`,
39
39
  inputSchema: ReportBlockerInputSchema,
40
+ view: "none",
40
41
  describeCall: "none",
41
42
  execute: (input) => Promise.resolve(ok({ kind: "blocker", reason: input.reason, message: "Blocker recorded. You are done — stop now and do not take further actions." })),
42
43
  });
@@ -63,6 +63,7 @@ export function createSkillTools(deps) {
63
63
  "and the first line that holds a query word — ranked by how many distinct query words the file " +
64
64
  "holds. Then `skill_read` the file. No matches returns a data variant.",
65
65
  inputSchema: SkillSearchInputSchema,
66
+ view: "none",
66
67
  describeCall: "none",
67
68
  execute: async ({ query, topK }) => {
68
69
  if (allowed.size === 0) {
@@ -121,6 +122,7 @@ export function createSkillTools(deps) {
121
122
  "found via skill_search). Confined to your declared skills. Undeclared " +
122
123
  "skills and missing paths return a data variant.",
123
124
  inputSchema: SkillReadInputSchema,
125
+ view: "none",
124
126
  describeCall: "none",
125
127
  execute: async ({ skill, path }) => {
126
128
  if (!allowed.has(skill)) {
@@ -43,6 +43,7 @@ export function createSubmitFileMetadataTool(cell) {
43
43
  "accepted=true means every file now has metadata — STOP. Otherwise drop " +
44
44
  "the unknownPaths and describe the remaining files.",
45
45
  inputSchema: SubmitFileMetadataInputSchema,
46
+ view: "none",
46
47
  describeCall: "none",
47
48
  execute: (input) => {
48
49
  const knownPaths = cell.knownPaths;
@@ -165,6 +165,7 @@ function createReadTableTool(deps) {
165
165
  "A larger table, or another format such as h5ad, is work for an analysis run.",
166
166
  inputSchema: readTableInput,
167
167
  outputSchema: ReadTableOutputSchema,
168
+ view: "none",
168
169
  describeCall: ({ path, entry }) => (entry === undefined ? path : `${path} : ${entry}`),
169
170
  describeResult: (_input, result) => `${String(result.rowCount)} rows`,
170
171
  execute: async ({ path, entry, columns }, ctx) => {
@@ -201,6 +202,7 @@ function createListArchiveTool(deps) {
201
202
  "`read_table` and `read_pdf` then read one of them as their `entry`.",
202
203
  inputSchema: listArchiveInput,
203
204
  outputSchema: ListArchiveOutputSchema,
205
+ view: "none",
204
206
  describeCall: ({ path }) => path,
205
207
  describeResult: (_input, result) => `${String(result.entries.length)} files`,
206
208
  execute: async ({ path }, ctx) => {
@@ -266,6 +268,7 @@ export function createReadPdfTool(deps) {
266
268
  "A scanned page with no text layer reads as an empty text.",
267
269
  inputSchema: readPdfInput,
268
270
  outputSchema: ReadPdfOutputSchema,
271
+ view: "none",
269
272
  describeCall: ({ path, entry, firstPage, lastPage }) => {
270
273
  const file = entry === undefined ? path : `${path} : ${entry}`;
271
274
  return firstPage === undefined && lastPage === undefined ? file : `${file}, pages ${String(firstPage ?? 1)}-${String(lastPage ?? "")}`;
@@ -359,6 +362,7 @@ function createWriteDerivedTool(deps) {
359
362
  "A name holds one file: a second write of the name with other content is refused.",
360
363
  inputSchema: writeDerivedInput,
361
364
  outputSchema: WrittenOutputSchema,
365
+ view: "none",
362
366
  describeCall: ({ name }) => name,
363
367
  describeResult: (_input, result) => result.path,
364
368
  execute: async ({ name, content, rows }, ctx) => {
@@ -400,6 +404,7 @@ function createRenderChartTool(deps) {
400
404
  "To show a chart in the chat, use `show_user`.",
401
405
  inputSchema: renderChartInput,
402
406
  outputSchema: WrittenOutputSchema,
407
+ view: "none",
403
408
  describeCall: ({ name }) => name,
404
409
  describeResult: (_input, result) => result.path,
405
410
  execute: async ({ name, spec, width }, ctx) => {
@@ -447,6 +452,7 @@ function createExportPdfTool(deps) {
447
452
  "Charts that `render_chart` wrote can appear in it as images. The PDF reads no URL.",
448
453
  inputSchema: exportPdfInput,
449
454
  outputSchema: ExportPdfOutputSchema,
455
+ view: "none",
450
456
  describeCall: ({ name }) => name,
451
457
  describeResult: (_input, result) => result.path,
452
458
  execute: async ({ name, markdown, title }, ctx) => {