@inflexa-ai/harness 0.28.1 → 0.30.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/dist/agents/conversation-agent.js +7 -4
- package/dist/agents/report-session-agent.js +1 -1
- package/dist/agents/sandbox/data-profiler.js +0 -1
- package/dist/agents/sandbox/immune-profiling-agent.js +3 -1
- package/dist/agents/sandbox/network-agent.js +0 -1
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/agents/sandbox/types.js +1 -1
- package/dist/app/data-profile-orientation.d.ts +52 -27
- package/dist/app/data-profile-orientation.js +331 -92
- package/dist/app/data-profile-view.d.ts +70 -0
- package/dist/app/data-profile-view.js +123 -0
- package/dist/app/synthesize-run.js +1 -1
- package/dist/config/resource-limits.d.ts +1 -1
- package/dist/config/resource-limits.js +1 -1
- package/dist/contracts/data-profile.d.ts +276 -25
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/profile-vocabulary.d.ts +103 -0
- package/dist/contracts/profile-vocabulary.js +449 -0
- package/dist/contracts/schemas/chat-parts.d.ts +12 -12
- package/dist/contracts/severity.d.ts +2 -2
- package/dist/contracts/target-dossier.d.ts +91 -91
- package/dist/data/safety-panel-schema.d.ts +4 -4
- package/dist/execution/input-signature.d.ts +16 -3
- package/dist/execution/input-signature.js +0 -0
- package/dist/execution/post-step-pipeline.js +1 -1
- package/dist/execution/staged-input.d.ts +4 -4
- package/dist/index.d.ts +7 -2
- package/dist/index.js +14 -0
- package/dist/input-scan/clustering.d.ts +63 -0
- package/dist/input-scan/clustering.js +208 -0
- package/dist/input-scan/companions.d.ts +30 -0
- package/dist/input-scan/companions.js +138 -0
- package/dist/input-scan/decoder/container_readout.py +171 -0
- package/dist/input-scan/detect-sets.d.ts +24 -0
- package/dist/input-scan/detect-sets.js +279 -0
- package/dist/input-scan/enrich.d.ts +62 -19
- package/dist/input-scan/enrich.js +132 -187
- package/dist/input-scan/formats.d.ts +13 -5
- package/dist/input-scan/formats.js +13 -5
- package/dist/input-scan/index.d.ts +10 -4
- package/dist/input-scan/index.js +7 -4
- package/dist/input-scan/markers.d.ts +21 -0
- package/dist/input-scan/markers.js +89 -0
- package/dist/input-scan/menu.d.ts +127 -0
- package/dist/input-scan/menu.js +0 -0
- package/dist/input-scan/quarantine.d.ts +17 -0
- package/dist/input-scan/quarantine.js +69 -0
- package/dist/input-scan/readout-budget.d.ts +32 -0
- package/dist/input-scan/readout-budget.js +61 -0
- package/dist/input-scan/readout.d.ts +33 -0
- package/dist/input-scan/readout.js +336 -0
- package/dist/input-scan/scan.d.ts +5 -4
- package/dist/input-scan/scan.js +5 -5
- package/dist/input-scan/set-types.d.ts +209 -0
- package/dist/input-scan/set-types.js +15 -0
- package/dist/input-scan/templates.d.ts +93 -0
- package/dist/input-scan/templates.js +369 -0
- package/dist/input-scan/tokens.d.ts +94 -0
- package/dist/input-scan/tokens.js +267 -0
- package/dist/input-scan/tuning.d.ts +55 -0
- package/dist/input-scan/tuning.js +55 -0
- package/dist/input-scan/types.d.ts +2 -2
- package/dist/loop/run-agent.d.ts +13 -1
- package/dist/loop/run-agent.js +2 -1
- package/dist/prompts/SOUL.d.ts +2 -2
- package/dist/prompts/SOUL.js +17 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +45 -71
- package/dist/prompts/planner.js +57 -10
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +3 -2
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -1
- package/dist/prompts/sandbox/data-profiler.d.ts +10 -1
- package/dist/prompts/sandbox/data-profiler.js +197 -53
- package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
- package/dist/prompts/sandbox/dna-methylation-agent.js +18 -12
- package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/immune-profiling-agent.js +6 -2
- package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/metabolomics-agent.js +8 -4
- package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
- package/dist/prompts/sandbox/microbiome-agent.js +12 -6
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +9 -6
- package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/spatial-omics-agent.js +4 -2
- package/dist/prompts/sandbox-standards.d.ts +1 -1
- package/dist/prompts/sandbox-standards.js +10 -7
- package/dist/providers/ai-sdk.js +34 -1
- package/dist/providers/reasoning.d.ts +65 -0
- package/dist/providers/reasoning.js +81 -0
- package/dist/providers/types.d.ts +14 -0
- package/dist/schemas/data-profile-schemas.d.ts +947 -84
- package/dist/schemas/data-profile-schemas.js +300 -115
- package/dist/schemas/run-synthesis.d.ts +4 -4
- package/dist/state/data-profile.d.ts +1 -1
- package/dist/state/index.d.ts +1 -1
- package/dist/state/vector-store.d.ts +13 -0
- package/dist/state/vector-store.js +8 -0
- package/dist/tasks/data-profile-absorb.d.ts +78 -0
- package/dist/tasks/data-profile-absorb.js +285 -0
- package/dist/tasks/data-profile-index.d.ts +34 -25
- package/dist/tasks/data-profile-index.js +160 -59
- package/dist/tasks/data-profile-resolve.d.ts +72 -0
- package/dist/tasks/data-profile-resolve.js +639 -0
- package/dist/tasks/data-profile.d.ts +16 -5
- package/dist/tasks/data-profile.js +270 -131
- package/dist/tools/bio/chembl.d.ts +1 -1
- package/dist/tools/bio/chembl.js +15 -10
- package/dist/tools/bio/comptox.js +5 -1
- package/dist/tools/bio/drug-gene-interactions.js +5 -2
- package/dist/tools/bio/gene-disease-evidence.d.ts +19 -9
- package/dist/tools/bio/gene-disease-evidence.js +78 -22
- package/dist/tools/bio/gene-preclinical-profile.d.ts +33 -13
- package/dist/tools/bio/gene-preclinical-profile.js +63 -22
- package/dist/tools/bio/lookup-annotation.d.ts +1 -0
- package/dist/tools/bio/lookup-annotation.js +28 -10
- package/dist/tools/bio/opentargets.d.ts +10 -2
- package/dist/tools/bio/opentargets.js +42 -13
- package/dist/tools/bio/pubchem.js +5 -2
- package/dist/tools/bio/pubmed.js +9 -3
- package/dist/tools/bio/search-clinical-trials.d.ts +25 -25
- package/dist/tools/bio/search-clinical-trials.js +56 -18
- package/dist/tools/bio/search-faers.d.ts +26 -6
- package/dist/tools/bio/search-faers.js +70 -18
- package/dist/tools/bio/search-gene.d.ts +19 -3
- package/dist/tools/bio/search-gene.js +69 -12
- package/dist/tools/bio/search-geo-datasets.js +4 -1
- package/dist/tools/bio/search-interactions.js +6 -2
- package/dist/tools/bio/target-safety.d.ts +2 -2
- package/dist/tools/bio/target-safety.js +3 -2
- package/dist/tools/lib/cbioportal-client.js +25 -6
- package/dist/tools/lib/chembl-client.d.ts +3 -1
- package/dist/tools/lib/chembl-client.js +6 -1
- package/dist/tools/lib/gwas-catalog-client.d.ts +7 -6
- package/dist/tools/lib/gwas-catalog-client.js +18 -6
- package/dist/tools/lib/openfda-client.d.ts +7 -5
- package/dist/tools/lib/openfda-client.js +17 -9
- package/dist/tools/lib/opentargets-client.d.ts +20 -0
- package/dist/tools/lib/opentargets-client.js +45 -0
- package/dist/tools/lib/pathway-client.d.ts +7 -0
- package/dist/tools/lib/pathway-client.js +68 -1
- package/dist/tools/research/context7-docs.js +3 -3
- package/dist/tools/research/generate-analogy-report.js +2 -2
- package/dist/tools/research/generate-plan.d.ts +3 -0
- package/dist/tools/research/generate-plan.js +64 -5
- package/dist/tools/research/inspect-data-profile.d.ts +48 -22
- package/dist/tools/research/inspect-data-profile.js +73 -54
- package/dist/tools/research/resolve-citation.js +8 -3
- package/dist/tools/research/search-arxiv.js +8 -3
- package/dist/tools/research/search-semantic-scholar.js +10 -4
- package/dist/tools/workspace/scan-inputs.d.ts +4 -4
- package/dist/tools/workspace/scan-inputs.js +13 -14
- package/dist/tools/workspace/workspace-search.d.ts +11 -2
- package/dist/tools/workspace/workspace-search.js +44 -16
- package/dist/workflows/sandbox-step.d.ts +6 -0
- package/dist/workflows/sandbox-step.js +31 -14
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +1 -1
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +38 -38
- package/dist/workflows/target-assessment/schemas.d.ts +12 -12
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +6 -6
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +6 -6
- package/dist/workflows/target-assessment/synthesis/index.d.ts +12 -12
- package/package.json +5 -3
- package/dist/input-scan/coverage.d.ts +0 -22
- package/dist/input-scan/coverage.js +0 -85
|
@@ -35,6 +35,12 @@ import { defineTool } from "../define-tool.js";
|
|
|
35
35
|
import { createListAvailablePackagesTool } from "../sandbox/list-available-packages.js";
|
|
36
36
|
import { createListAvailableRefsTool } from "../sandbox/list-available-refs.js";
|
|
37
37
|
import { createReportBlockerToolFor } from "../sandbox/report-blocker.js";
|
|
38
|
+
import { searchGeoDatasetsTool } from "../bio/search-geo-datasets.js";
|
|
39
|
+
import { createNcbiTools } from "../bio/keys.js";
|
|
40
|
+
import { queryDocsTool, resolveLibraryIdTool } from "./context7-docs.js";
|
|
41
|
+
import { searchArxivTool } from "./search-arxiv.js";
|
|
42
|
+
import { createSearchGithubReposTool } from "./search-github-repos.js";
|
|
43
|
+
import { createSearchSemanticScholarTool } from "./search-semantic-scholar.js";
|
|
38
44
|
import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from "../../app/data-profile-orientation.js";
|
|
39
45
|
import { DEFAULT_SANDBOX_MAX_STEPS } from "../../config/resource-limits.js";
|
|
40
46
|
import { plannerPrompt } from "../../prompts/planner.js";
|
|
@@ -49,10 +55,17 @@ import { insertPlan, loadDataProfileStatus, loadPlan } from "../../state/index.j
|
|
|
49
55
|
/** Sub-agent identity for the planner — provenance only. */
|
|
50
56
|
const PLANNER_AGENT_ID = "planner";
|
|
51
57
|
/**
|
|
52
|
-
* Budget for the planner's internal loop: one draft/submit
|
|
53
|
-
* correction retries and headroom.
|
|
58
|
+
* Budget for the planner's internal loop: the research phase, one draft/submit
|
|
59
|
+
* attempt, correction retries, and headroom.
|
|
60
|
+
*
|
|
61
|
+
* The number is deliberately far above what a plan costs. A well-grounded plan
|
|
62
|
+
* still costs two or three calls, because the prompt gates the research phase on
|
|
63
|
+
* what the seed does not already answer. What the ceiling must never do is stop
|
|
64
|
+
* a planner that is searching correctly. A capped run takes the forced wrap-up
|
|
65
|
+
* path and submits the plan that it had, not the plan that it was building.
|
|
66
|
+
* Thus the wall-clock guard, not this number, is what bounds the worst case.
|
|
54
67
|
*/
|
|
55
|
-
const PLANNER_MAX_ITERATIONS =
|
|
68
|
+
const PLANNER_MAX_ITERATIONS = 200;
|
|
56
69
|
/** Wall-clock guard for a single plan-generation invocation. */
|
|
57
70
|
const PLAN_TIMEOUT_MS = 600_000;
|
|
58
71
|
// ── Diagnostic bounds ───────────────────────────────────────────────
|
|
@@ -71,7 +84,7 @@ const MAX_LOGGED_ISSUE_CHARS = 240;
|
|
|
71
84
|
const MAX_LOGGED_REJECTIONS = 6;
|
|
72
85
|
/** Excerpt of the planner's last words — the one artifact that explains a run ending on prose. */
|
|
73
86
|
const MAX_LOGGED_PROSE_CHARS = 800;
|
|
74
|
-
/** Tool-call trace length.
|
|
87
|
+
/** Tool-call trace length. A long research phase passes it, and `toolCallsTruncated` marks the record when it does. */
|
|
75
88
|
const MAX_LOGGED_TOOL_CALLS = 48;
|
|
76
89
|
/** Trim to `max`, marking the cut so a truncated value never reads as a complete one. */
|
|
77
90
|
function bounded(value, max) {
|
|
@@ -629,6 +642,44 @@ const OUTCOME_LEVEL = {
|
|
|
629
642
|
invalid_parent_plan: "warn",
|
|
630
643
|
parent_plan_load_failed: "error",
|
|
631
644
|
};
|
|
645
|
+
/**
|
|
646
|
+
* The search tools of the planner — built one time, not per invocation.
|
|
647
|
+
*
|
|
648
|
+
* The set answers the two questions that a seed cannot. "What did a study of
|
|
649
|
+
* this kind do before?" reaches the public dataset records and the literature.
|
|
650
|
+
* "What does this package actually give me?" reaches the developer docs. The
|
|
651
|
+
* planner reads its reference and package inventories from the seed, thus the
|
|
652
|
+
* docs pair is what turns a package name into a real function.
|
|
653
|
+
*
|
|
654
|
+
* A tool here never writes and never computes. Thus the worst outcome of a
|
|
655
|
+
* needless call is latency, and the prompt is what bounds that.
|
|
656
|
+
*/
|
|
657
|
+
function buildPlannerSearchTools(deps) {
|
|
658
|
+
const bioKeys = deps.bioKeys;
|
|
659
|
+
const ncbi = createNcbiTools(bioKeys);
|
|
660
|
+
return [
|
|
661
|
+
// Comparable public studies — the design, the platform, and the sample
|
|
662
|
+
// count that a plan for the same assay must answer to.
|
|
663
|
+
searchGeoDatasetsTool,
|
|
664
|
+
// Prior methods, in three corpora that do not overlap: the biomedical
|
|
665
|
+
// literature, the statistics and machine-learning preprints, and the
|
|
666
|
+
// cross-field index that ranks a method paper best.
|
|
667
|
+
ncbi.pubmed,
|
|
668
|
+
searchArxivTool,
|
|
669
|
+
createSearchSemanticScholarTool({ ...(bioKeys.semanticScholar === undefined ? {} : { apiKey: bioKeys.semanticScholar }) }),
|
|
670
|
+
// The same approach as running code — a pipeline that someone published.
|
|
671
|
+
createSearchGithubReposTool({ githubToken: bioKeys.github }),
|
|
672
|
+
// The API of a staged package, so a step names a function that exists.
|
|
673
|
+
resolveLibraryIdTool,
|
|
674
|
+
queryDocsTool,
|
|
675
|
+
// The environment itself. The seed already carries a rendered census of
|
|
676
|
+
// both stores. These two tools are for the narrow second look: one
|
|
677
|
+
// collection of the reference store, or one package name that a step is
|
|
678
|
+
// about to import.
|
|
679
|
+
createListAvailableRefsTool(deps.refStorePath === undefined ? {} : { refStorePath: deps.refStorePath }),
|
|
680
|
+
createListAvailablePackagesTool(deps.packagesFile === undefined ? {} : { packagesFile: deps.packagesFile }),
|
|
681
|
+
];
|
|
682
|
+
}
|
|
632
683
|
/** Build the `generate_plan` tool bound to its provider and pool. */
|
|
633
684
|
export function createGeneratePlanTool(deps) {
|
|
634
685
|
const baseLogger = (deps.logger ?? createNoopLogger()).named("generate-plan");
|
|
@@ -806,11 +857,19 @@ export function createGeneratePlanTool(deps) {
|
|
|
806
857
|
parentPlanId: input.parentPlanId ?? null,
|
|
807
858
|
};
|
|
808
859
|
const innerTools = buildInnerTools(holder, trace, persistCtx, deps.pool, deps.resourcePolicy, logger);
|
|
860
|
+
// Built here rather than at construction: a `describeCall` hook reads no
|
|
861
|
+
// dep, thus the tool must stay constructible from an empty bag. The tool
|
|
862
|
+
// definitions are identical across invocations, thus the request prefix
|
|
863
|
+
// that the cache keys on does not move.
|
|
864
|
+
const searchTools = buildPlannerSearchTools(deps);
|
|
809
865
|
const planner = {
|
|
810
866
|
id: PLANNER_AGENT_ID,
|
|
811
867
|
systemPrompt: composeSystemPrompt(plannerInstructions(deps.resourcePolicy)),
|
|
812
868
|
model: deps.conversation.model,
|
|
813
|
-
tools
|
|
869
|
+
// The search tools come first, and the terminal tools come last.
|
|
870
|
+
// The order is the order of the prompt, and it is stable across
|
|
871
|
+
// every invocation, thus the cached request prefix holds.
|
|
872
|
+
tools: [...searchTools, ...innerTools.terminal],
|
|
814
873
|
maxIterations: PLANNER_MAX_ITERATIONS,
|
|
815
874
|
};
|
|
816
875
|
// Merge the outer abort signal with a wall-clock timeout — either
|
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
* **There is no data-profile file.** The profiler's `runs/data-profile/` scratch tree
|
|
9
9
|
* is deleted when profiling completes; the profile's only durable home is the
|
|
10
10
|
* `cortex_analysis_state` row this tool reads. That makes this the single authoritative
|
|
11
|
-
* source for dataset facts
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* source for dataset facts — the identity fields, the resolved groups with their slots,
|
|
12
|
+
* the dimensions with their observations, and the partition census — and it is why the
|
|
13
|
+
* record is served in full rather than summarized: an agent that cannot pull a fact here
|
|
14
|
+
* has no fallback but to re-derive it from the raw bytes.
|
|
14
15
|
*
|
|
15
|
-
* Bounded by construction. Like `inspect_run`, the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Bounded by construction. Like `inspect_run`, the member scope is explicitly paged and
|
|
17
|
+
* always reports `total` and `hasMore`: an elided tail is a fact the model can see and
|
|
18
|
+
* act on, never a silent truncation. The annotated-member count and the dataset's file
|
|
19
|
+
* count are reported as two distinct figures, because a profile annotating eight members
|
|
20
|
+
* of several thousand files is correct and a single number would read as a tiny dataset.
|
|
18
21
|
*
|
|
19
22
|
* Lifecycle states are data variants, not errors (the `defineTool` contract): a missing,
|
|
20
23
|
* in-flight, failed, or stale profile is an ordinary, expected outcome the model must be
|
|
@@ -50,45 +53,68 @@ interface OverviewOutput extends ProfileEnvelope {
|
|
|
50
53
|
readonly cellType?: string | null;
|
|
51
54
|
readonly condition?: string | null;
|
|
52
55
|
readonly experimentalDesign?: string;
|
|
53
|
-
|
|
56
|
+
/** Dataset-wide findings the agent wrote, from whichever field the snapshot carries them in. */
|
|
57
|
+
readonly caveats?: string[];
|
|
54
58
|
readonly accessions?: string[];
|
|
55
|
-
/** How many
|
|
59
|
+
/** How many member records `scope:"files"` would page through — NOT the dataset's size. */
|
|
56
60
|
readonly describedFileCount: number;
|
|
57
61
|
/**
|
|
58
62
|
* How many files the dataset holds, or `null` when the snapshot cannot say (one
|
|
59
|
-
* written before
|
|
60
|
-
* `describedFileCount` because a profile describing eight
|
|
61
|
-
* is correct under this capability, and a single count would read as a dataset
|
|
62
|
-
* eight files.
|
|
63
|
+
* written before any structure record existed). Reported separately from
|
|
64
|
+
* `describedFileCount` because a profile describing eight members of several thousand
|
|
65
|
+
* files is correct under this capability, and a single count would read as a dataset
|
|
66
|
+
* of eight files.
|
|
63
67
|
*/
|
|
64
68
|
readonly datasetFileCount: number | null;
|
|
65
|
-
/** How many
|
|
66
|
-
readonly
|
|
69
|
+
/** How many groups `scope:"groups"` would return; `null` on a snapshot that carries no structure. */
|
|
70
|
+
readonly groupCount: number | null;
|
|
67
71
|
/** Present when the two counts differ — says where the rest of the dataset is described. */
|
|
68
72
|
readonly structureNote?: string;
|
|
73
|
+
/**
|
|
74
|
+
* The census, on a snapshot resolved under the partition: kept files, per-group
|
|
75
|
+
* counts, the unclassified sweep, and the quarantine with its reasons.
|
|
76
|
+
*/
|
|
77
|
+
readonly partition?: DataProfileResult["partition"];
|
|
69
78
|
readonly coverage?: DataProfileResult["coverage"];
|
|
70
79
|
}
|
|
71
80
|
/**
|
|
72
|
-
* The dataset's structure. A snapshot
|
|
73
|
-
*
|
|
74
|
-
*
|
|
81
|
+
* The dataset's structure. A snapshot carrying none reports `available: false` rather
|
|
82
|
+
* than an empty list: "this profile predates the structure" and "this dataset has no
|
|
83
|
+
* structure" are different facts, and only one of them is true.
|
|
84
|
+
*
|
|
85
|
+
* A snapshot written under the previous model is served through `kinds`/`axes`, labelled
|
|
86
|
+
* — the structure exists, and an agent must not be told the dataset has none.
|
|
75
87
|
*/
|
|
76
|
-
interface
|
|
77
|
-
readonly scope: "
|
|
88
|
+
interface GroupsOutput extends ProfileEnvelope {
|
|
89
|
+
readonly scope: "groups";
|
|
78
90
|
readonly available: boolean;
|
|
91
|
+
/** Authored under the previous model — `kinds`/`axes` rather than resolved groups. */
|
|
92
|
+
readonly legacy?: boolean;
|
|
93
|
+
readonly groups?: DataProfileResult["groups"];
|
|
94
|
+
readonly dimensions?: DataProfileResult["dimensions"];
|
|
95
|
+
readonly probes?: DataProfileResult["probes"];
|
|
96
|
+
readonly partition?: DataProfileResult["partition"];
|
|
97
|
+
readonly recipe?: DataProfileResult["recipe"];
|
|
79
98
|
readonly kinds?: DataProfileResult["kinds"];
|
|
80
99
|
readonly axes?: DataProfileResult["axes"];
|
|
81
100
|
readonly coverage?: DataProfileResult["coverage"];
|
|
82
101
|
readonly message?: string;
|
|
83
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* The members the profile describes individually — annotated members on a resolved
|
|
105
|
+
* snapshot, the notable-file records on an older one. Never the dataset: `datasetFileCount`
|
|
106
|
+
* on the overview is what says how big that is.
|
|
107
|
+
*/
|
|
84
108
|
interface FilesOutput extends ProfileEnvelope {
|
|
85
109
|
readonly scope: "files";
|
|
86
110
|
readonly page: number;
|
|
87
111
|
readonly pageSize: number;
|
|
88
|
-
/** Every
|
|
112
|
+
/** Every member record the profile holds — not just this page. */
|
|
89
113
|
readonly total: number;
|
|
90
114
|
/** True when records remain past this page. Truncation is always stated, never silent. */
|
|
91
115
|
readonly hasMore: boolean;
|
|
116
|
+
/** How many files the dataset holds, so a page of 20 never reads as the whole of it. */
|
|
117
|
+
readonly datasetFileCount: number | null;
|
|
92
118
|
readonly files: DataProfileFile[];
|
|
93
119
|
}
|
|
94
120
|
/**
|
|
@@ -133,9 +159,9 @@ interface FailedOutput {
|
|
|
133
159
|
readonly failedAt: string | null;
|
|
134
160
|
readonly message: string;
|
|
135
161
|
}
|
|
136
|
-
export type InspectDataProfileOutput = OverviewOutput |
|
|
162
|
+
export type InspectDataProfileOutput = OverviewOutput | GroupsOutput | FilesOutput | AbsentOutput | PendingOutput | FailedOutput;
|
|
137
163
|
export declare function createInspectDataProfileTool(pool: Pool): import("../define-tool.js").Tool<{
|
|
138
|
-
scope?: "overview" | "
|
|
164
|
+
scope?: "overview" | "groups" | "files" | undefined;
|
|
139
165
|
page?: number | undefined;
|
|
140
166
|
pageSize?: number | undefined;
|
|
141
167
|
}, InspectDataProfileOutput>;
|
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
* **There is no data-profile file.** The profiler's `runs/data-profile/` scratch tree
|
|
9
9
|
* is deleted when profiling completes; the profile's only durable home is the
|
|
10
10
|
* `cortex_analysis_state` row this tool reads. That makes this the single authoritative
|
|
11
|
-
* source for dataset facts
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* source for dataset facts — the identity fields, the resolved groups with their slots,
|
|
12
|
+
* the dimensions with their observations, and the partition census — and it is why the
|
|
13
|
+
* record is served in full rather than summarized: an agent that cannot pull a fact here
|
|
14
|
+
* has no fallback but to re-derive it from the raw bytes.
|
|
14
15
|
*
|
|
15
|
-
* Bounded by construction. Like `inspect_run`, the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Bounded by construction. Like `inspect_run`, the member scope is explicitly paged and
|
|
17
|
+
* always reports `total` and `hasMore`: an elided tail is a fact the model can see and
|
|
18
|
+
* act on, never a silent truncation. The annotated-member count and the dataset's file
|
|
19
|
+
* count are reported as two distinct figures, because a profile annotating eight members
|
|
20
|
+
* of several thousand files is correct and a single number would read as a tiny dataset.
|
|
18
21
|
*
|
|
19
22
|
* Lifecycle states are data variants, not errors (the `defineTool` contract): a missing,
|
|
20
23
|
* in-flight, failed, or stale profile is an ordinary, expected outcome the model must be
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
*/
|
|
29
32
|
import { ok } from "neverthrow";
|
|
30
33
|
import { z } from "zod";
|
|
34
|
+
import { profileCaveats, profileDatasetFileCount, profileFileRecords, profileGroups } from "../../app/data-profile-view.js";
|
|
31
35
|
import { scopeResource } from "../../auth/types.js";
|
|
32
36
|
import { unwrapOrThrow } from "../../lib/result.js";
|
|
33
37
|
import { loadDataProfileStatus } from "../../state/index.js";
|
|
@@ -37,31 +41,19 @@ import { defineTool } from "../define-tool.js";
|
|
|
37
41
|
const DEFAULT_PAGE_SIZE = 20;
|
|
38
42
|
/** Ceiling on `pageSize` — a page is a context-window budget, not a dump. */
|
|
39
43
|
const MAX_PAGE_SIZE = 100;
|
|
40
|
-
/**
|
|
41
|
-
* How many files the dataset holds, as far as the snapshot can establish it.
|
|
42
|
-
*
|
|
43
|
-
* Coverage counts the scanned tree, so it is the authority where present. Summed kind
|
|
44
|
-
* counts are the fallback. A snapshot with neither returns `null` rather than the
|
|
45
|
-
* described-file count, which would be a dataset size the row cannot support.
|
|
46
|
-
*/
|
|
47
|
-
function datasetFileCount(result) {
|
|
48
|
-
if (result.coverage)
|
|
49
|
-
return result.coverage.total;
|
|
50
|
-
if (result.kinds && result.kinds.length > 0)
|
|
51
|
-
return result.kinds.reduce((sum, kind) => sum + kind.count, 0);
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
44
|
/** Serve each per-file record with a path that resolves in the frame of the caller. */
|
|
55
45
|
function rootFilePaths(files, analysisId) {
|
|
56
46
|
return files.map((file) => ({ ...file, path: toAnalysisRootPath(analysisId, file.path) }));
|
|
57
47
|
}
|
|
58
48
|
/**
|
|
59
|
-
* Serve each
|
|
49
|
+
* Serve each group with a display pattern that resolves in the frame of the caller.
|
|
60
50
|
*
|
|
61
|
-
* A
|
|
62
|
-
*
|
|
63
|
-
* only re-roots the pattern.
|
|
51
|
+
* A pattern is a path with varying segments, thus it takes the same projection as a file
|
|
52
|
+
* path. Its placeholders survive it: the projection only re-roots the pattern.
|
|
64
53
|
*/
|
|
54
|
+
function rootGroupPatterns(groups, analysisId) {
|
|
55
|
+
return groups.map((group) => ({ ...group, displayPattern: toAnalysisRootPath(analysisId, group.displayPattern) }));
|
|
56
|
+
}
|
|
65
57
|
function rootKindPatterns(kinds, analysisId) {
|
|
66
58
|
return kinds.map((kind) => ({ ...kind, pathPattern: toAnalysisRootPath(analysisId, kind.pathPattern) }));
|
|
67
59
|
}
|
|
@@ -103,20 +95,21 @@ export function createInspectDataProfileTool(pool) {
|
|
|
103
95
|
id: "inspect_data_profile",
|
|
104
96
|
description: "Read this analysis's data profile — the AUTHORITATIVE record of what the input dataset is: " +
|
|
105
97
|
"organism and taxon id, scientific domain and subtype, tissue, cell type, condition, experimental design, " +
|
|
106
|
-
"dataset-wide
|
|
107
|
-
"
|
|
108
|
-
"the files described individually. " +
|
|
98
|
+
"dataset-wide caveats, public accessions, the GROUPS of file the dataset is made of, the DIMENSIONS that vary " +
|
|
99
|
+
"across it with the evidence behind each, and the file census (kept, unclassified, quarantined). " +
|
|
109
100
|
"There is NO data-profile file in the workspace — this tool is the only way to read it. Do not search for one, " +
|
|
110
101
|
"and do not rediscover these facts by listing or reading the raw input files. " +
|
|
111
102
|
"Call it before you reason about the data (planning, writing analysis code, interpreting results). " +
|
|
112
|
-
"scope:'overview' (the default) returns the dataset-level orientation
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
103
|
+
"scope:'overview' (the default) returns the dataset-level orientation plus the partition accounting — how many " +
|
|
104
|
+
"files were kept, how many landed in no group, how many were quarantined — and how many members are described " +
|
|
105
|
+
"individually. That last figure and the dataset's file count differ on purpose: a large dataset is described by " +
|
|
106
|
+
"its groups, not file by file. " +
|
|
107
|
+
"scope:'groups' returns the groups with their derived counts, display patterns, and slots, plus the dimensions " +
|
|
108
|
+
"with their observations, reconciliations, and probe outcomes — this is where a dataset of thousands of files is " +
|
|
109
|
+
"described. A profile written under the previous model returns its kinds and axes, labelled as such. " +
|
|
110
|
+
"scope:'files' returns the individually annotated members, paged: page (1-based, default 1) and pageSize (default 20, max 100), " +
|
|
118
111
|
"always with the true total and hasMore, so you can see exactly what you have not read yet. " +
|
|
119
|
-
"For the paths of files no record describes, list the workspace tree. " +
|
|
112
|
+
"For the paths of files no record describes, use scope:'groups' for the structure and list the workspace tree for the paths. " +
|
|
120
113
|
"The state field says what you got: 'ready'; 'stale' (a profile is returned but may not describe the current " +
|
|
121
114
|
"inputs — staleReason says why); 'pending' (profiling is still running); 'failed'; or 'absent' (never profiled, " +
|
|
122
115
|
"or the analysis has no input files). " +
|
|
@@ -126,19 +119,19 @@ export function createInspectDataProfileTool(pool) {
|
|
|
126
119
|
"needs re-running rather than diagnosing the data itself.",
|
|
127
120
|
inputSchema: z.object({
|
|
128
121
|
scope: z
|
|
129
|
-
.enum(["overview", "
|
|
122
|
+
.enum(["overview", "groups", "files"])
|
|
130
123
|
.optional()
|
|
131
|
-
.describe("'overview' (default): dataset-level facts,
|
|
132
|
-
"'
|
|
133
|
-
"'files': paged records for the individually
|
|
134
|
-
page: z.number().int().min(1).optional().describe("1-based page of
|
|
124
|
+
.describe("'overview' (default): dataset-level facts, the partition accounting, the annotated-member count, and the dataset file count. " +
|
|
125
|
+
"'groups': the groups the dataset is made of, with their slots, and the dimensions that vary across it. " +
|
|
126
|
+
"'files': paged records for the individually annotated members."),
|
|
127
|
+
page: z.number().int().min(1).optional().describe("1-based page of member records. Only used when scope is 'files'. Default 1."),
|
|
135
128
|
pageSize: z
|
|
136
129
|
.number()
|
|
137
130
|
.int()
|
|
138
131
|
.min(1)
|
|
139
132
|
.max(MAX_PAGE_SIZE)
|
|
140
133
|
.optional()
|
|
141
|
-
.describe(`
|
|
134
|
+
.describe(`Member records per page. Only used when scope is 'files'. Default ${DEFAULT_PAGE_SIZE}, max ${MAX_PAGE_SIZE}.`),
|
|
142
135
|
}),
|
|
143
136
|
// `execute` defaults both fields internally, and the hook applies the same
|
|
144
137
|
// defaults. Without them the ordinary call, which names no field at all,
|
|
@@ -160,8 +153,8 @@ export function createInspectDataProfileTool(pool) {
|
|
|
160
153
|
if (status.status === "failed") {
|
|
161
154
|
// A failure that DID leave a prior profile on the row never reaches
|
|
162
155
|
// here — it is served as `stale`, where `stalenessReasons` names the
|
|
163
|
-
// failed re-profile
|
|
164
|
-
//
|
|
156
|
+
// failed re-profile. This branch is the case with no prior profile to
|
|
157
|
+
// serve at all.
|
|
165
158
|
return ok({
|
|
166
159
|
state: "failed",
|
|
167
160
|
error: status.error,
|
|
@@ -188,14 +181,16 @@ export function createInspectDataProfileTool(pool) {
|
|
|
188
181
|
? { state: "stale", staleReason: reasons.join("; "), profiledAt: result.profiledAt }
|
|
189
182
|
: { state: "ready", profiledAt: result.profiledAt };
|
|
190
183
|
const scope = input.scope ?? "overview";
|
|
184
|
+
const describedFiles = profileFileRecords(result);
|
|
185
|
+
const groups = profileGroups(result);
|
|
191
186
|
if (scope === "overview") {
|
|
192
|
-
const dataset =
|
|
193
|
-
const described =
|
|
187
|
+
const dataset = profileDatasetFileCount(result);
|
|
188
|
+
const described = describedFiles.length;
|
|
194
189
|
const structureNote = dataset === null
|
|
195
190
|
? "This profile predates the dataset-structure record, so only the individually described files are known; " +
|
|
196
191
|
"list the workspace tree for the rest."
|
|
197
192
|
: dataset !== described
|
|
198
|
-
? `${described} of ${dataset} files are described individually. Call scope:'
|
|
193
|
+
? `${described} of ${dataset} files are described individually. Call scope:'groups' for what the rest are, ` +
|
|
199
194
|
"and list the workspace tree for their paths."
|
|
200
195
|
: undefined;
|
|
201
196
|
return ok({
|
|
@@ -209,40 +204,63 @@ export function createInspectDataProfileTool(pool) {
|
|
|
209
204
|
cellType: result.cellType,
|
|
210
205
|
condition: result.condition,
|
|
211
206
|
experimentalDesign: result.experimentalDesign,
|
|
212
|
-
|
|
207
|
+
caveats: profileCaveats(result),
|
|
213
208
|
accessions: result.accessions,
|
|
214
209
|
describedFileCount: described,
|
|
215
210
|
datasetFileCount: dataset,
|
|
216
|
-
|
|
211
|
+
// Presence, not truthiness: a completed profile over a tree the scan kept
|
|
212
|
+
// nothing of carries `groups: []`, and "zero groups" is a finding — reading
|
|
213
|
+
// it as "no structure record" would report the analysis unprofiled.
|
|
214
|
+
groupCount: result.groups !== undefined || result.kinds !== undefined ? groups.length : null,
|
|
217
215
|
...(structureNote ? { structureNote } : {}),
|
|
216
|
+
...(result.partition ? { partition: result.partition } : {}),
|
|
218
217
|
...(result.coverage ? { coverage: result.coverage } : {}),
|
|
219
218
|
});
|
|
220
219
|
}
|
|
221
|
-
if (scope === "
|
|
222
|
-
if (
|
|
220
|
+
if (scope === "groups") {
|
|
221
|
+
if (result.groups !== undefined) {
|
|
222
|
+
return ok({
|
|
223
|
+
...envelope,
|
|
224
|
+
scope: "groups",
|
|
225
|
+
available: true,
|
|
226
|
+
groups: rootGroupPatterns(result.groups, resourceId),
|
|
227
|
+
...(result.dimensions ? { dimensions: result.dimensions } : {}),
|
|
228
|
+
...(result.probes ? { probes: result.probes } : {}),
|
|
229
|
+
...(result.partition ? { partition: result.partition } : {}),
|
|
230
|
+
...(result.recipe ? { recipe: result.recipe } : {}),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (result.kinds === undefined) {
|
|
223
234
|
return ok({
|
|
224
235
|
...envelope,
|
|
225
|
-
scope: "
|
|
236
|
+
scope: "groups",
|
|
226
237
|
available: false,
|
|
227
|
-
message: "This profile was taken before the dataset-structure record existed, so it carries no
|
|
238
|
+
message: "This profile was taken before the dataset-structure record existed, so it carries no groups. " +
|
|
228
239
|
"That is a fact about the profile, not about the dataset: use scope:'files' and the workspace " +
|
|
229
240
|
"listing tools, or re-profile the analysis.",
|
|
230
241
|
});
|
|
231
242
|
}
|
|
243
|
+
// The structure exists; only its vocabulary is older. Serving it labelled beats
|
|
244
|
+
// reporting the scope unavailable, which would tell an agent the dataset has no
|
|
245
|
+
// structure when the row plainly holds some.
|
|
232
246
|
return ok({
|
|
233
247
|
...envelope,
|
|
234
|
-
scope: "
|
|
248
|
+
scope: "groups",
|
|
235
249
|
available: true,
|
|
250
|
+
legacy: true,
|
|
236
251
|
kinds: rootKindPatterns(result.kinds, resourceId),
|
|
237
252
|
...(result.axes ? { axes: result.axes } : {}),
|
|
238
253
|
...(result.coverage ? { coverage: result.coverage } : {}),
|
|
254
|
+
message: "Authored under the previous model: these are kinds and axes, not resolved groups and dimensions. " +
|
|
255
|
+
"Counts and patterns were the profiler's own claims rather than a computed partition, and there is no " +
|
|
256
|
+
"unclassified sweep or quarantine accounting on a profile of that era.",
|
|
239
257
|
});
|
|
240
258
|
}
|
|
241
259
|
const pageSize = input.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
242
260
|
const page = input.page ?? 1;
|
|
243
|
-
const total =
|
|
261
|
+
const total = describedFiles.length;
|
|
244
262
|
const start = (page - 1) * pageSize;
|
|
245
|
-
const files =
|
|
263
|
+
const files = describedFiles.slice(start, start + pageSize);
|
|
246
264
|
return ok({
|
|
247
265
|
...envelope,
|
|
248
266
|
scope: "files",
|
|
@@ -250,6 +268,7 @@ export function createInspectDataProfileTool(pool) {
|
|
|
250
268
|
pageSize,
|
|
251
269
|
total,
|
|
252
270
|
hasMore: start + files.length < total,
|
|
271
|
+
datasetFileCount: profileDatasetFileCount(result),
|
|
253
272
|
files: rootFilePaths(files, resourceId),
|
|
254
273
|
});
|
|
255
274
|
},
|
|
@@ -4,10 +4,15 @@ import { defineTool } from "../define-tool.js";
|
|
|
4
4
|
export function createResolveCitationTool(resolver) {
|
|
5
5
|
return defineTool({
|
|
6
6
|
id: "resolve_citation",
|
|
7
|
-
description: "Verify one supplied citation against applicable bibliographic authorities
|
|
8
|
-
"
|
|
7
|
+
description: "Verify one supplied citation against the applicable bibliographic authorities — the DOI registry, Crossref, PubMed, arXiv and Semantic " +
|
|
8
|
+
"Scholar — and report where they agree. " +
|
|
9
|
+
"Use this for 'does this reference exist and does its metadata agree?', not for topical literature discovery: pubmed, " +
|
|
10
|
+
"search_semantic_scholar and search_arxiv are the discovery tools.\n" +
|
|
11
|
+
"ACCEPTED IDENTIFIERS: a DOI ('10.1038/nature12373'), a PMID ('23945592'), an arXiv ID ('2301.00001'), or raw bibliographic text. Each " +
|
|
12
|
+
"authority that the input cannot address is marked not_applicable rather than queried.\n" +
|
|
9
13
|
"Returns source-labelled records, field comparisons, conflicts, coverage, and one of verified, metadata_mismatch, " +
|
|
10
|
-
"not_found, unverifiable, or inconclusive. Source failure never becomes not_found
|
|
14
|
+
"not_found, unverifiable, or inconclusive. Source failure never becomes not_found — an unreachable authority reports unavailable, and " +
|
|
15
|
+
"not_found means the authorities that answered hold no such record.",
|
|
11
16
|
inputSchema: CitationInputSchema,
|
|
12
17
|
describeCall: ({ citation }) => `verify ${citation}`,
|
|
13
18
|
execute: async (input, ctx) => ok(await resolver.resolveOne(input, { signal: ctx.signal })),
|
|
@@ -7,9 +7,14 @@ export { parseArxivAtom } from "../../literature/sources/arxiv.js";
|
|
|
7
7
|
const source = createArxivSource({ maxRetries: 3, retryDelayMs: 1_000, timeoutMs: 90_000 });
|
|
8
8
|
export const searchArxivTool = defineTool({
|
|
9
9
|
id: "search_arxiv",
|
|
10
|
-
description: "Search arXiv for
|
|
11
|
-
"
|
|
12
|
-
"
|
|
10
|
+
description: "Search arXiv — the open preprint server of Cornell University — for work in machine learning, physics, math, quantitative biology, control " +
|
|
11
|
+
"theory, economics and the related quantitative fields. Returns id, title, abstract, authors, publication date, arXiv categories, and URLs.\n" +
|
|
12
|
+
"It carries PREPRINTS, thus a paper here may not be peer reviewed, and it is where a method appears months before a journal holds it. For the " +
|
|
13
|
+
"clinical and biomedical literature use pubmed, which indexes MEDLINE; for cross-domain coverage of any field use search_semantic_scholar.\n" +
|
|
14
|
+
"ACCEPTED IDENTIFIERS: free text in `query`, and arXiv category codes in `categories` ('cs.LG', 'q-bio.GN', 'math.OC'). An arXiv ID " +
|
|
15
|
+
"('2301.00001') also matches as free text.\n" +
|
|
16
|
+
"ABSENCE IS NORMAL: an empty papers array means arXiv holds nothing for the query, and `success: false` with an `error` string means the service " +
|
|
17
|
+
"could not be reached. Report either one and continue, do not retry unchanged.",
|
|
13
18
|
inputSchema: z.object({
|
|
14
19
|
query: z.string().describe('Free-text query. Example: "adaptive control feedback stabilization".'),
|
|
15
20
|
categories: z
|
|
@@ -13,10 +13,16 @@ export function createSearchSemanticScholarTool(options = {}) {
|
|
|
13
13
|
});
|
|
14
14
|
return defineTool({
|
|
15
15
|
id: "search_semantic_scholar",
|
|
16
|
-
description: "Search Semantic Scholar for
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
16
|
+
description: "Search Semantic Scholar — the cross-disciplinary literature corpus of the Allen Institute for AI, which indexes over 200M papers across " +
|
|
17
|
+
"biology, medicine, machine learning, physics, math, economics and engineering. Returns paper id, title, abstract, year, venue, citation " +
|
|
18
|
+
"count, authors, and external IDs (DOI, arXiv). Its citation counts are what rank a field by influence, and its breadth is what makes it the " +
|
|
19
|
+
"tool for a cross-domain analogical search.\n" +
|
|
20
|
+
"For the clinical and biomedical literature alone, pubmed indexes MEDLINE more precisely; for preprints in the quantitative fields use " +
|
|
21
|
+
"search_arxiv.\n" +
|
|
22
|
+
"ACCEPTED IDENTIFIERS: free text in `query`. A DOI or an arXiv ID also matches as free text, and each result carries its DOI and arXiv ID " +
|
|
23
|
+
"back for a citation.\n" +
|
|
24
|
+
"ABSENCE IS NORMAL: an empty papers array means the corpus holds nothing for the query, and `success: false` with an `error` string means the " +
|
|
25
|
+
"service could not be reached (its unkeyed rate limit lands here). Report either one and continue, do not retry unchanged.",
|
|
20
26
|
inputSchema: z.object({
|
|
21
27
|
query: z
|
|
22
28
|
.string()
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* with the observed shapes, or wants a directory the injected manifest summarised, can
|
|
8
8
|
* get the evidence to group differently.
|
|
9
9
|
*
|
|
10
|
-
* Dependency-bearing factory. The walk
|
|
11
|
-
* the workspace read seam; only
|
|
12
|
-
* (see `input-scan/enrich.ts`).
|
|
10
|
+
* Dependency-bearing factory. The walk, the shape observation, and every
|
|
11
|
+
* prefix-sufficient header readout run in-process over the workspace read seam; only a
|
|
12
|
+
* footer-indexed container reaches into the sandbox (see `input-scan/enrich.ts`).
|
|
13
13
|
*/
|
|
14
14
|
import type { SandboxClient } from "../../sandbox/client.js";
|
|
15
15
|
import type { SandboxRef } from "../../sandbox/types.js";
|
|
@@ -17,7 +17,7 @@ import type { WorkspaceFilesystem } from "../../workspace/filesystem.js";
|
|
|
17
17
|
export interface ScanInputsDeps {
|
|
18
18
|
readonly workspaceFs: WorkspaceFilesystem;
|
|
19
19
|
readonly analysisId: string;
|
|
20
|
-
/** Omit to
|
|
20
|
+
/** Omit to skip the container readouts; the prefix readouts run regardless. */
|
|
21
21
|
readonly sandboxClient?: SandboxClient;
|
|
22
22
|
readonly sandbox?: SandboxRef;
|
|
23
23
|
readonly workflowId: string;
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* with the observed shapes, or wants a directory the injected manifest summarised, can
|
|
8
8
|
* get the evidence to group differently.
|
|
9
9
|
*
|
|
10
|
-
* Dependency-bearing factory. The walk
|
|
11
|
-
* the workspace read seam; only
|
|
12
|
-
* (see `input-scan/enrich.ts`).
|
|
10
|
+
* Dependency-bearing factory. The walk, the shape observation, and every
|
|
11
|
+
* prefix-sufficient header readout run in-process over the workspace read seam; only a
|
|
12
|
+
* footer-indexed container reaches into the sandbox (see `input-scan/enrich.ts`).
|
|
13
13
|
*/
|
|
14
14
|
import { ok } from "neverthrow";
|
|
15
15
|
import { z } from "zod";
|
|
@@ -38,17 +38,16 @@ export function createScanInputsTool(deps) {
|
|
|
38
38
|
root: path.replace(/^\/+/, "").replace(/\/+$/, ""),
|
|
39
39
|
signal: ctx.signal,
|
|
40
40
|
});
|
|
41
|
-
const shapes =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
: scan.manifest.shapes;
|
|
41
|
+
const shapes = await enrichShapes({
|
|
42
|
+
shapes: scan.manifest.shapes,
|
|
43
|
+
session: ctx.session,
|
|
44
|
+
fs: deps.workspaceFs,
|
|
45
|
+
...(deps.sandboxClient && deps.sandbox ? { sandboxClient: deps.sandboxClient, sandbox: deps.sandbox } : {}),
|
|
46
|
+
mountRoot: `/${deps.analysisId}`,
|
|
47
|
+
execId: `${deps.workflowId}:${deps.stepId}:${deps.nextFunctionId()}`,
|
|
48
|
+
deadlineMs: deps.deadlineMs(),
|
|
49
|
+
emit: ctx.emit,
|
|
50
|
+
});
|
|
52
51
|
const manifest = { ...scan.manifest, shapes };
|
|
53
52
|
return ok({
|
|
54
53
|
path: manifest.root,
|
|
@@ -12,14 +12,23 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import type { Pool } from "pg";
|
|
14
14
|
import type { EmbeddingProvider } from "../../providers/types.js";
|
|
15
|
+
/**
|
|
16
|
+
* The workspace path a hit addresses, or `undefined` for a pattern entry. Resolved here,
|
|
17
|
+
* once, across every index era — group/dimension/kind entries and previous-era entity
|
|
18
|
+
* entries (`entity` in metadata) are patterns; everything else is a file whose path is
|
|
19
|
+
* stamped in metadata or, on rows older than the stamp, is the id minus the resource
|
|
20
|
+
* segment. The consumer branches on presence, nothing else.
|
|
21
|
+
*/
|
|
22
|
+
export declare function fileTargetOf(id: string, metadata: unknown): string | undefined;
|
|
15
23
|
export declare function createWorkspaceSearchTool(pool: Pool, embedding: EmbeddingProvider): import("../define-tool.js").Tool<{
|
|
16
24
|
query: string;
|
|
17
25
|
limit: number;
|
|
18
|
-
type?: "output" | "input" | "synthesis" | "summary" | "input-kind" | undefined;
|
|
26
|
+
type?: "output" | "input" | "synthesis" | "summary" | "input-group" | "input-dimension" | "input-kind" | undefined;
|
|
19
27
|
}, {
|
|
20
28
|
results: {
|
|
29
|
+
metadata: unknown;
|
|
30
|
+
path?: string | undefined;
|
|
21
31
|
id: string;
|
|
22
32
|
score: number;
|
|
23
|
-
metadata: unknown;
|
|
24
33
|
}[];
|
|
25
34
|
}>;
|