@inflexa-ai/harness 0.17.0 → 0.19.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/report-builder.d.ts +2 -0
- package/dist/agents/report-builder.js +2 -0
- package/dist/agents/report-session-agent.d.ts +56 -0
- package/dist/agents/report-session-agent.js +72 -0
- package/dist/app/report-session-runtime.d.ts +72 -0
- package/dist/app/report-session-runtime.js +157 -0
- package/dist/app/spawn-report-session.d.ts +85 -0
- package/dist/app/spawn-report-session.js +83 -0
- package/dist/contracts/chat-events.d.ts +9 -0
- package/dist/contracts/index.d.ts +6 -2
- package/dist/contracts/index.js +3 -1
- package/dist/contracts/organ-system.d.ts +49 -0
- package/dist/contracts/organ-system.js +67 -0
- package/dist/contracts/report-blocks.d.ts +564 -0
- package/dist/contracts/report-blocks.js +117 -0
- package/dist/contracts/report-reference.d.ts +459 -0
- package/dist/contracts/report-reference.js +256 -0
- package/dist/contracts/schemas/chat-events.d.ts +2 -0
- package/dist/contracts/schemas/chat-events.js +2 -0
- package/dist/contracts/severity.d.ts +15 -0
- package/dist/contracts/severity.js +10 -0
- package/dist/contracts/target-dossier.d.ts +8675 -12972
- package/dist/contracts/target-dossier.js +534 -591
- package/dist/data/safety-panel-schema.d.ts +18 -5
- package/dist/data/safety-panel-schema.js +4 -4
- package/dist/execution/artifact-metadata.js +1 -0
- package/dist/execution/report-runner.js +1 -3
- package/dist/execution/run-synthesis.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +19 -0
- package/dist/lib/async-utils.d.ts +16 -4
- package/dist/lib/async-utils.js +20 -15
- package/dist/lib/host-concurrency.js +1 -0
- package/dist/loop/__fixtures__/scripted-provider.js +23 -2
- package/dist/loop/run-agent.js +58 -10
- package/dist/loop/tool-detail.d.ts +40 -0
- package/dist/loop/tool-detail.js +35 -3
- package/dist/loop/types.d.ts +13 -0
- package/dist/memory/thread-history.d.ts +10 -0
- package/dist/memory/thread-history.js +12 -1
- package/dist/memory/thread-store.d.ts +4 -4
- package/dist/memory/thread-store.js +18 -9
- package/dist/prompts/report-session.d.ts +1 -0
- package/dist/prompts/report-session.js +89 -0
- package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
- package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
- package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
- package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
- package/dist/prompts/target-assessment/investigation/index.js +3 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
- package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
- package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
- package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
- package/dist/providers/__fixtures__/session.d.ts +5 -4
- package/dist/providers/__fixtures__/session.js +4 -4
- package/dist/providers/ai-sdk.d.ts +77 -0
- package/dist/providers/ai-sdk.js +149 -19
- package/dist/providers/errors.d.ts +14 -0
- package/dist/providers/errors.js +103 -18
- package/dist/providers/types.d.ts +16 -0
- package/dist/providers/types.js +10 -1
- package/dist/report-model/block-walk.d.ts +56 -0
- package/dist/report-model/block-walk.js +89 -0
- package/dist/report-model/draft-finish.d.ts +60 -0
- package/dist/report-model/draft-finish.js +56 -0
- package/dist/report-model/draft-operations.d.ts +125 -0
- package/dist/report-model/draft-operations.js +341 -0
- package/dist/report-model/draft-read.d.ts +59 -0
- package/dist/report-model/draft-read.js +109 -0
- package/dist/report-model/draft.d.ts +51 -0
- package/dist/report-model/draft.js +32 -0
- package/dist/report-model/fixture-resolver.d.ts +8 -0
- package/dist/report-model/fixture-resolver.js +252 -0
- package/dist/report-model/pin-snapshot.d.ts +30 -0
- package/dist/report-model/pin-snapshot.js +40 -0
- package/dist/report-model/reference-resolver.d.ts +113 -0
- package/dist/report-model/reference-resolver.js +60 -0
- package/dist/report-model/structural-validation.d.ts +22 -0
- package/dist/report-model/structural-validation.js +68 -0
- package/dist/report-model/validate.d.ts +72 -0
- package/dist/report-model/validate.js +95 -0
- package/dist/report-render/chart.d.ts +33 -0
- package/dist/report-render/chart.js +538 -0
- package/dist/report-render/page.d.ts +127 -0
- package/dist/report-render/page.js +307 -0
- package/dist/report-render/references.d.ts +32 -0
- package/dist/report-render/references.js +44 -0
- package/dist/report-render/render.d.ts +25 -0
- package/dist/report-render/render.js +125 -0
- package/dist/report-render/script-json.d.ts +13 -0
- package/dist/report-render/script-json.js +15 -0
- package/dist/report-render/types.d.ts +39 -0
- package/dist/report-render/types.js +10 -0
- package/dist/report-render/value-bridge.d.ts +62 -0
- package/dist/report-render/value-bridge.js +82 -0
- package/dist/report-render/views/chart-view.d.ts +11 -0
- package/dist/report-render/views/chart-view.js +23 -0
- package/dist/report-render/views/page-view.d.ts +20 -0
- package/dist/report-render/views/page-view.js +49 -0
- package/dist/report-render/views/prose.d.ts +28 -0
- package/dist/report-render/views/prose.js +75 -0
- package/dist/report-render/views/references-view.d.ts +20 -0
- package/dist/report-render/views/references-view.js +64 -0
- package/dist/report-render/views/values.d.ts +42 -0
- package/dist/report-render/views/values.js +43 -0
- package/dist/runtime/assemble.d.ts +15 -0
- package/dist/runtime/assemble.js +28 -6
- package/dist/runtime/boot.d.ts +3 -3
- package/dist/runtime/boot.js +15 -4
- package/dist/runtime/dbos.d.ts +4 -4
- package/dist/state/artifacts.d.ts +16 -0
- package/dist/state/artifacts.js +18 -0
- package/dist/state/init.js +54 -0
- package/dist/state/purge-analysis.js +2 -0
- package/dist/state/report-session-state.d.ts +112 -0
- package/dist/state/report-session-state.js +150 -0
- package/dist/state/report-versions.d.ts +129 -0
- package/dist/state/report-versions.js +167 -0
- package/dist/state/snapshot-parse.d.ts +34 -0
- package/dist/state/snapshot-parse.js +82 -0
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/ad-hoc-router.d.ts +7 -2
- package/dist/tools/ad-hoc-router.js +2 -1
- package/dist/tools/bio/chembl.js +1 -0
- package/dist/tools/bio/comptox.js +1 -0
- package/dist/tools/bio/drug-gene-interactions.js +1 -0
- package/dist/tools/bio/gene-disease-evidence.js +1 -0
- package/dist/tools/bio/gene-preclinical-profile.js +1 -0
- package/dist/tools/bio/keys.d.ts +3 -2
- package/dist/tools/bio/keys.js +3 -2
- package/dist/tools/bio/lookup-annotation.js +1 -0
- package/dist/tools/bio/opentargets.d.ts +3 -3
- package/dist/tools/bio/opentargets.js +4 -3
- package/dist/tools/bio/pubchem.js +1 -0
- package/dist/tools/bio/search-clinical-trials.js +1 -0
- package/dist/tools/bio/search-faers.js +1 -0
- package/dist/tools/bio/search-geo-datasets.js +1 -0
- package/dist/tools/bio/search-interactions.js +1 -0
- package/dist/tools/bio/target-safety.d.ts +4 -6
- package/dist/tools/bio/target-safety.js +7 -6
- package/dist/tools/define-tool.d.ts +19 -12
- package/dist/tools/define-tool.js +4 -2
- package/dist/tools/display/show-user.js +1 -0
- package/dist/tools/iterate-report.js +2 -0
- package/dist/tools/lib/chembl-client.d.ts +2 -0
- package/dist/tools/lib/chembl-client.js +1 -0
- package/dist/tools/lib/monarch-client.d.ts +42 -0
- package/dist/tools/lib/monarch-client.js +100 -0
- package/dist/tools/report/build-report.js +1 -0
- package/dist/tools/report/mint-preview-url.js +1 -0
- package/dist/tools/report/preview-snapshot.js +1 -0
- package/dist/tools/report/submit-report.js +1 -0
- package/dist/tools/report/version-fs.js +4 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +241 -0
- package/dist/tools/report-authoring/authoring-tools.js +460 -0
- package/dist/tools/report-session/index.d.ts +8 -0
- package/dist/tools/report-session/index.js +8 -0
- package/dist/tools/report-session/preview-report.d.ts +90 -0
- package/dist/tools/report-session/preview-report.js +245 -0
- package/dist/tools/research/context7-docs.js +2 -0
- package/dist/tools/research/generate-analogy-report.js +1 -0
- package/dist/tools/research/generate-plan.js +7 -2
- package/dist/tools/research/inspect-data-profile.js +4 -0
- package/dist/tools/research/literature-reviewer.js +1 -0
- package/dist/tools/research/search-arxiv.js +1 -0
- package/dist/tools/research/search-github-repos.js +1 -0
- package/dist/tools/research/search-semantic-scholar.js +1 -0
- package/dist/tools/sandbox/list-available-packages.js +23 -0
- package/dist/tools/sandbox/list-available-refs.js +36 -0
- package/dist/tools/sandbox/report-blocker.js +1 -0
- package/dist/tools/sandbox/skills.js +2 -0
- package/dist/tools/workspace/file-stat.js +1 -0
- package/dist/tools/workspace/show-file.js +2 -0
- package/dist/tools/workspace/show-plan.js +1 -0
- package/dist/workflows/execute-analysis.d.ts +5 -1
- package/dist/workflows/execute-analysis.js +5 -5
- package/dist/workflows/execute-target-assessment.d.ts +13 -2
- package/dist/workflows/execute-target-assessment.js +78 -16
- package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
- package/dist/workflows/target-assessment/assemblers/index.js +1 -1
- package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
- package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
- package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
- package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
- package/dist/workflows/target-assessment/collectors/index.js +48 -5
- package/dist/workflows/target-assessment/coverage.d.ts +43 -27
- package/dist/workflows/target-assessment/coverage.js +33 -29
- package/dist/workflows/target-assessment/fanout/index.js +1 -0
- package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
- package/dist/workflows/target-assessment/investigation/index.js +419 -0
- package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
- package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
- package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
- package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
- package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
- package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
- package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
- package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
- package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
- package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
- package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
- package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
- package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
- package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
- package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
- package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
- package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
- package/dist/workflows/target-assessment/metrics.d.ts +1 -6
- package/dist/workflows/target-assessment/metrics.js +1 -13
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +2147 -807
- package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
- package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
- package/dist/workflows/target-assessment/phase5-persist.js +56 -173
- package/dist/workflows/target-assessment/schemas.d.ts +46 -1
- package/dist/workflows/target-assessment/schemas.js +19 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +26 -62
- package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +27 -154
- package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
- package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
- package/dist/workflows/target-assessment/synthesis/index.js +4 -1
- package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
- package/package.json +4 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The render-and-preview tool of a report session.
|
|
3
|
+
*
|
|
4
|
+
* The tool renders the current draft of a thread. It runs the finish first, because a mid-composition
|
|
5
|
+
* draft is not a `ReportDocument`. A gap list returns as data, and no render runs. On a pass the tool
|
|
6
|
+
* resolves each reference through the injected resolver, bridges the resolved values into the render model,
|
|
7
|
+
* and renders the page.
|
|
8
|
+
*
|
|
9
|
+
* The page and its staged image assets land in `report-sessions/{threadId}/` under the workspace root. That
|
|
10
|
+
* namespace belongs to this path alone, thus the tool never writes under the old `previews/` or `reports/`
|
|
11
|
+
* trees. The result carries the absolute page path, thus a local host shows the page with no seam.
|
|
12
|
+
*
|
|
13
|
+
* The result carries no access grant. `PreviewPublisher` authorizes the URL space
|
|
14
|
+
* `previews/{analysisId}/{previewId}` (`contracts/content-url.ts`), and that space cannot name a page of
|
|
15
|
+
* this tree. A hosted view of a session page is a later capability, with a URL space of its own.
|
|
16
|
+
*
|
|
17
|
+
* Each degraded condition is a typed outcome in the ok channel: a session refusal, a gap list, a resolver
|
|
18
|
+
* absence, an unresolved reference, a bridge mismatch, a render problem, a figure that escapes the
|
|
19
|
+
* workspace root, and a write failure. The tool never throws for one of them. The filesystem speaks the
|
|
20
|
+
* throw protocol, and the workspace-root seam signals an unresolvable resource the same way. Thus the
|
|
21
|
+
* write runs through the `tryFs` glue, which turns a genuine fault into the ok-channel outcome and lets a
|
|
22
|
+
* control-flow exception propagate.
|
|
23
|
+
*/
|
|
24
|
+
import { err, ok } from "neverthrow";
|
|
25
|
+
import { copyFile, mkdir, writeFile } from "node:fs/promises";
|
|
26
|
+
import { extname, join } from "node:path";
|
|
27
|
+
import { z } from "zod";
|
|
28
|
+
import { resolveWorkspacePath } from "../../workspace/paths.js";
|
|
29
|
+
import { createNoopLogger } from "../../lib/console-logger.js";
|
|
30
|
+
import { describeFsError, tryFsWrite } from "../../lib/fs-result.js";
|
|
31
|
+
import { defaultErrorFields } from "../../lib/logger.js";
|
|
32
|
+
import { finishDraft } from "../../report-model/draft-finish.js";
|
|
33
|
+
import { resolveDocumentReferences } from "../../report-model/validate.js";
|
|
34
|
+
import { bridgeValues } from "../../report-render/value-bridge.js";
|
|
35
|
+
import { renderReportPage } from "../../report-render/render.js";
|
|
36
|
+
import { defineTool } from "../define-tool.js";
|
|
37
|
+
import { openReportThread } from "../report-authoring/authoring-tools.js";
|
|
38
|
+
/** The empty input. The tool renders the current draft of the thread, thus it needs no field. */
|
|
39
|
+
const previewReportInput = z.object({});
|
|
40
|
+
/**
|
|
41
|
+
* The staged file name of one bound image: its content hash with the original extension.
|
|
42
|
+
*
|
|
43
|
+
* The colon of the `algorithm:hex` hash is not safe in a file name and in a relative source, thus the slug
|
|
44
|
+
* replaces each non-word run with one dash. The hash is unique, thus two figures with one basename cannot
|
|
45
|
+
* collide on the staged name.
|
|
46
|
+
*/
|
|
47
|
+
function assetFileName(file) {
|
|
48
|
+
const slug = file.hash.replace(/[^a-z0-9]+/gi, "-");
|
|
49
|
+
return slug + extname(file.path);
|
|
50
|
+
}
|
|
51
|
+
/** The relative source of a staged figure. The page directory is self-contained, thus the source is `assets/<name>`. */
|
|
52
|
+
function figureSourcePolicy(file) {
|
|
53
|
+
return `assets/${assetFileName(file)}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pair each block with the resolved value of its binding, in document order.
|
|
57
|
+
*
|
|
58
|
+
* A value-bearing kind carries the resolved value of its one reference, looked up by the block id from
|
|
59
|
+
* the map that the resolution pass filled. A no-value kind carries none. Where a reference sits in a
|
|
60
|
+
* block is the knowledge of `block-walk.ts`, thus this walk reads the block id alone and never a binding
|
|
61
|
+
* field.
|
|
62
|
+
*
|
|
63
|
+
* The switch is exhaustive over the eight block kinds. A ninth kind reaches the end with no return, and
|
|
64
|
+
* the declared return type fails the build. Thus the walk cannot drop a kind in silence. A value-bearing
|
|
65
|
+
* block reaches here only when its reference resolved, because an unresolved reference short-circuits
|
|
66
|
+
* before this walk.
|
|
67
|
+
*/
|
|
68
|
+
function collectResolutions(blocks, resolvedByBlock) {
|
|
69
|
+
const resolutions = [];
|
|
70
|
+
const visit = (block) => {
|
|
71
|
+
switch (block.kind) {
|
|
72
|
+
case "section":
|
|
73
|
+
resolutions.push({ blockId: block.id, kind: "section" });
|
|
74
|
+
for (const child of block.blocks) {
|
|
75
|
+
visit(child);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
case "text":
|
|
79
|
+
case "claim":
|
|
80
|
+
case "citation":
|
|
81
|
+
resolutions.push({ blockId: block.id, kind: block.kind });
|
|
82
|
+
return;
|
|
83
|
+
case "metric":
|
|
84
|
+
case "table":
|
|
85
|
+
case "chart":
|
|
86
|
+
case "figure": {
|
|
87
|
+
const resolved = resolvedByBlock.get(block.id);
|
|
88
|
+
if (resolved !== undefined) {
|
|
89
|
+
resolutions.push({ blockId: block.id, kind: block.kind, resolved });
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
for (const block of blocks) {
|
|
96
|
+
visit(block);
|
|
97
|
+
}
|
|
98
|
+
return resolutions;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolve each reference of the document, and split the resolutions from the unresolved references.
|
|
102
|
+
*
|
|
103
|
+
* The shared `resolveDocumentReferences` walks the tree, resolves each reference under the concurrency
|
|
104
|
+
* bound, and runs the chart-encoding match, thus the preview and the record gate refuse the same
|
|
105
|
+
* references. On a clean pass the resolutions map onto the render model in document order.
|
|
106
|
+
*/
|
|
107
|
+
async function resolveDocument(document, resolver, snapshot) {
|
|
108
|
+
const { resolvedByBlock, failures } = await resolveDocumentReferences(document.sections, snapshot, resolver);
|
|
109
|
+
if (failures.length > 0) {
|
|
110
|
+
return { resolutions: [], unresolved: failures };
|
|
111
|
+
}
|
|
112
|
+
return { resolutions: collectResolutions(document.sections, resolvedByBlock), unresolved: [] };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the page root, stage each bound image, and write the page.
|
|
116
|
+
*
|
|
117
|
+
* The workspace-root seam signals an unresolvable resource by a throw, thus the resolution sits inside
|
|
118
|
+
* the protection and its throw becomes a value. Each `fs` call runs through `tryFsWrite`, the one
|
|
119
|
+
* sanctioned `fs` guard, thus a genuine I/O fault becomes a value and a control-flow exception such as a
|
|
120
|
+
* cancellation propagates.
|
|
121
|
+
*
|
|
122
|
+
* A bound figure names a snapshot path, which is untrusted. A registered `../../` path escapes the root,
|
|
123
|
+
* thus the containment test runs before any copy and reuses the one workspace-path resolver. A source
|
|
124
|
+
* outside the root refuses and names the block, and no copy runs.
|
|
125
|
+
*/
|
|
126
|
+
async function renderToWorkspace(args) {
|
|
127
|
+
let root;
|
|
128
|
+
try {
|
|
129
|
+
root = args.resolveWorkspaceRoot(args.analysisId);
|
|
130
|
+
}
|
|
131
|
+
catch (cause) {
|
|
132
|
+
return err({ kind: "fs", error: { type: "read_failed", op: "preview.resolveWorkspaceRoot", path: args.analysisId, cause } });
|
|
133
|
+
}
|
|
134
|
+
const sessionDir = join(root, "report-sessions", args.threadId);
|
|
135
|
+
const assetsDir = join(sessionDir, "assets");
|
|
136
|
+
const pagePath = join(sessionDir, "index.html");
|
|
137
|
+
// Each bound figure, contained and deduplicated by its staged name, mapped to its host source.
|
|
138
|
+
const sources = new Map();
|
|
139
|
+
for (const resolution of args.resolutions) {
|
|
140
|
+
if (resolution.kind !== "figure" || resolution.resolved.type !== "file") {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const file = resolution.resolved;
|
|
144
|
+
const name = assetFileName(file);
|
|
145
|
+
if (sources.has(name)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const resolved = resolveWorkspacePath({ workspaceRoot: root, analysisId: args.analysisId, path: file.path });
|
|
149
|
+
if (resolved.kind !== "ok") {
|
|
150
|
+
return err({ kind: "figure-out-of-scope", blockId: resolution.blockId, path: file.path });
|
|
151
|
+
}
|
|
152
|
+
sources.set(name, resolved.absolute);
|
|
153
|
+
}
|
|
154
|
+
const madeSession = await tryFsWrite("preview.mkdir", () => mkdir(sessionDir, { recursive: true }), { path: sessionDir });
|
|
155
|
+
if (madeSession.isErr()) {
|
|
156
|
+
return err({ kind: "fs", error: madeSession.error });
|
|
157
|
+
}
|
|
158
|
+
if (sources.size > 0) {
|
|
159
|
+
const madeAssets = await tryFsWrite("preview.mkdir", () => mkdir(assetsDir, { recursive: true }), { path: assetsDir });
|
|
160
|
+
if (madeAssets.isErr()) {
|
|
161
|
+
return err({ kind: "fs", error: madeAssets.error });
|
|
162
|
+
}
|
|
163
|
+
for (const [name, source] of sources) {
|
|
164
|
+
const copied = await tryFsWrite("preview.copyFile", () => copyFile(source, join(assetsDir, name)), { path: source });
|
|
165
|
+
if (copied.isErr()) {
|
|
166
|
+
return err({ kind: "fs", error: copied.error });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const wrote = await tryFsWrite("preview.writeFile", () => writeFile(pagePath, args.page, "utf8"), { path: pagePath });
|
|
171
|
+
if (wrote.isErr()) {
|
|
172
|
+
return err({ kind: "fs", error: wrote.error });
|
|
173
|
+
}
|
|
174
|
+
return ok(pagePath);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Make the render-and-preview tool over the session-state gateway and the render seams.
|
|
178
|
+
*
|
|
179
|
+
* The tool reads the thread id from the scope of the call, and it loads the state through the gateway. Thus
|
|
180
|
+
* one factory serves every thread. The tool holds no per-session value, and it resolves the root of the
|
|
181
|
+
* analysis of the call.
|
|
182
|
+
*/
|
|
183
|
+
export function createPreviewReportTool(deps) {
|
|
184
|
+
const logger = (deps.logger ?? createNoopLogger()).named("preview-report");
|
|
185
|
+
return defineTool({
|
|
186
|
+
id: "preview_report",
|
|
187
|
+
description: "Render the current draft to a self-contained HTML page, and give back the page path. " +
|
|
188
|
+
"The tool finishes the draft first: an incomplete draft gives back the gap list, and no page renders. " +
|
|
189
|
+
"On a pass it resolves each reference, stages each bound image beside the page, and writes the page. " +
|
|
190
|
+
"Use it to see the report, and to confirm that each reference resolves.",
|
|
191
|
+
inputSchema: previewReportInput,
|
|
192
|
+
executionMode: "inline",
|
|
193
|
+
describeCall: "none",
|
|
194
|
+
execute: async (_input, ctx) => {
|
|
195
|
+
const opened = await openReportThread(deps.gateway, ctx.session.scope);
|
|
196
|
+
if (opened.isErr()) {
|
|
197
|
+
return ok({ outcome: "refused", refusal: opened.error });
|
|
198
|
+
}
|
|
199
|
+
const { threadId, analysisId, state } = opened.value;
|
|
200
|
+
const { document: draft, snapshot } = state;
|
|
201
|
+
const finished = finishDraft(draft, snapshot);
|
|
202
|
+
if (!finished.valid) {
|
|
203
|
+
return ok({ outcome: "gaps", gaps: finished.gaps });
|
|
204
|
+
}
|
|
205
|
+
const document = finished.document;
|
|
206
|
+
if (deps.resolver === undefined) {
|
|
207
|
+
return ok({ outcome: "resolver-unavailable" });
|
|
208
|
+
}
|
|
209
|
+
const { resolutions, unresolved } = await resolveDocument(document, deps.resolver, snapshot);
|
|
210
|
+
if (unresolved.length > 0) {
|
|
211
|
+
return ok({ outcome: "unresolved-references", unresolved });
|
|
212
|
+
}
|
|
213
|
+
const bridged = bridgeValues(resolutions, figureSourcePolicy);
|
|
214
|
+
if (bridged.isErr()) {
|
|
215
|
+
return ok({ outcome: "bridge-mismatch", mismatches: bridged.error });
|
|
216
|
+
}
|
|
217
|
+
const rendered = renderReportPage(document, bridged.value);
|
|
218
|
+
if (rendered.isErr()) {
|
|
219
|
+
return ok({ outcome: "render-problems", problems: rendered.error });
|
|
220
|
+
}
|
|
221
|
+
const written = await renderToWorkspace({
|
|
222
|
+
resolveWorkspaceRoot: deps.resolveWorkspaceRoot,
|
|
223
|
+
analysisId,
|
|
224
|
+
threadId,
|
|
225
|
+
resolutions,
|
|
226
|
+
page: rendered.value,
|
|
227
|
+
});
|
|
228
|
+
if (written.isErr()) {
|
|
229
|
+
const failure = written.error;
|
|
230
|
+
if (failure.kind === "figure-out-of-scope") {
|
|
231
|
+
// A bound figure names a path outside the workspace root. The record gate refuses the
|
|
232
|
+
// same figure, thus the preview refuses it too and names the block.
|
|
233
|
+
logger.warn("a bound figure escapes the workspace root", { threadId, analysisId, blockId: failure.blockId, path: failure.path });
|
|
234
|
+
return ok({ outcome: "figure-out-of-scope", blockId: failure.blockId, path: failure.path });
|
|
235
|
+
}
|
|
236
|
+
// An unresolvable root, a full volume, and a denied write each arrive here as a value. The
|
|
237
|
+
// tool contract is ok-channel data for each degraded condition, thus the fault becomes an
|
|
238
|
+
// outcome. The log keeps the full fault, because the outcome carries the description alone.
|
|
239
|
+
logger.warn("the page did not land", { threadId, analysisId, ...defaultErrorFields(failure.error.cause) });
|
|
240
|
+
return ok({ outcome: "write-failed", detail: describeFsError(failure.error) });
|
|
241
|
+
}
|
|
242
|
+
return ok({ outcome: "rendered", pagePath: written.value });
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
}
|
|
@@ -25,6 +25,7 @@ export const resolveLibraryIdTool = defineTool({
|
|
|
25
25
|
libraryName: z.string().describe("Package or library name to search for (e.g., 'scanpy', 'pysam', 'lifelines')."),
|
|
26
26
|
query: z.string().describe("What you need help with — used to rank results by relevance (e.g., 'how to run differential expression')."),
|
|
27
27
|
}),
|
|
28
|
+
describeCall: "none",
|
|
28
29
|
execute: async ({ libraryName, query }) => {
|
|
29
30
|
const result = await apiFetchValidated(`${CONTEXT7_BASE}/search?query=${encodeURIComponent(libraryName)}&topic=${encodeURIComponent(query)}`, Context7SearchResponseSchema, { maxRetries: 1 });
|
|
30
31
|
if (result.isErr())
|
|
@@ -48,6 +49,7 @@ export const queryDocsTool = defineTool({
|
|
|
48
49
|
libraryId: z.string().describe("Context7 library ID from resolve-library-id (e.g., '/scverse/scanpy')."),
|
|
49
50
|
query: z.string().describe("Specific question about the library (e.g., 'rank_genes_groups function parameters and usage')."),
|
|
50
51
|
}),
|
|
52
|
+
describeCall: "none",
|
|
51
53
|
execute: async ({ libraryId, query }) => {
|
|
52
54
|
const result = await apiFetchValidated(`${CONTEXT7_BASE}/docs?libraryId=${encodeURIComponent(libraryId)}&query=${encodeURIComponent(query)}`, Context7DocsResponseSchema, { maxRetries: 1 });
|
|
53
55
|
if (result.isErr())
|
|
@@ -193,6 +193,7 @@ export function createGenerateAnalogyReportTool(deps) {
|
|
|
193
193
|
"latency on the same failure). Surface the message and ask the user to " +
|
|
194
194
|
"narrow the problem.",
|
|
195
195
|
inputSchema: generateAnalogyReportInputSchema,
|
|
196
|
+
describeCall: "none",
|
|
196
197
|
execute: async (input, ctx) => {
|
|
197
198
|
const childSession = forSubAgent(ctx.session, AGENT_ID);
|
|
198
199
|
// Phase 1+2: drive the research agent over the cross-domain toolset.
|
|
@@ -30,6 +30,7 @@ import { composeSystemPrompt } from "../../agents/system-prompt.js";
|
|
|
30
30
|
import { DEFAULT_SALVAGE_ITERATIONS, runToTerminal } from "../../loop/run-to-terminal.js";
|
|
31
31
|
import { passthroughStep } from "../../loop/run-step.js";
|
|
32
32
|
import { forSubAgent, scopeResource } from "../../auth/types.js";
|
|
33
|
+
import { effectiveDeadlineMs } from "../../providers/types.js";
|
|
33
34
|
import { defineTool } from "../define-tool.js";
|
|
34
35
|
import { createListAvailablePackagesTool } from "../sandbox/list-available-packages.js";
|
|
35
36
|
import { createListAvailableRefsTool } from "../sandbox/list-available-refs.js";
|
|
@@ -344,6 +345,7 @@ function buildInnerTools(holder, trace, persistCtx, pool, resourcePolicy, logger
|
|
|
344
345
|
// structured issue reporting.
|
|
345
346
|
plan: z.union([PlannerPlanSchema, z.unknown()]).describe("Candidate plan; malformed values are reported in issues."),
|
|
346
347
|
}),
|
|
348
|
+
describeCall: "none",
|
|
347
349
|
execute: async (input) => {
|
|
348
350
|
if (holder.outcome !== null) {
|
|
349
351
|
trace.duplicateTerminalCalls++;
|
|
@@ -416,6 +418,7 @@ function buildInnerTools(holder, trace, persistCtx, pool, resourcePolicy, logger
|
|
|
416
418
|
question: z.string().min(1),
|
|
417
419
|
questionContext: z.string().optional(),
|
|
418
420
|
}),
|
|
421
|
+
describeCall: "none",
|
|
419
422
|
execute: async (input) => {
|
|
420
423
|
if (holder.outcome !== null) {
|
|
421
424
|
trace.duplicateTerminalCalls++;
|
|
@@ -670,6 +673,7 @@ export function createGeneratePlanTool(deps) {
|
|
|
670
673
|
.optional()
|
|
671
674
|
.describe("The planId of the prior plan being iterated on. Set only when " + "the user is asking for modifications to an existing plan."),
|
|
672
675
|
}),
|
|
676
|
+
describeCall: ({ researchQuestion }) => researchQuestion,
|
|
673
677
|
execute: async (input, ctx) => {
|
|
674
678
|
const analysisId = scopeResource(ctx.session.scope).resourceId;
|
|
675
679
|
// Bound per invocation: `analysisId` comes from the call-time session, not from
|
|
@@ -773,6 +777,7 @@ export function createGeneratePlanTool(deps) {
|
|
|
773
777
|
...(input.priorRuns ? ["", "## Prior Run Results", input.priorRuns] : []),
|
|
774
778
|
...(input.userConstraints ? ["", "## User Constraints", input.userConstraints] : []),
|
|
775
779
|
].join("\n");
|
|
780
|
+
const planTimeoutMs = effectiveDeadlineMs(deps.conversation.provider, PLAN_TIMEOUT_MS);
|
|
776
781
|
// Opens the invocation. Two things are only knowable here and both are
|
|
777
782
|
// first-order suspects when a planner runs long and returns nothing: the size
|
|
778
783
|
// of the seed it was handed, and which of its blocks made it that size. The
|
|
@@ -785,7 +790,7 @@ export function createGeneratePlanTool(deps) {
|
|
|
785
790
|
model: deps.conversation.model,
|
|
786
791
|
maxIterations: PLANNER_MAX_ITERATIONS,
|
|
787
792
|
salvageIterations: DEFAULT_SALVAGE_ITERATIONS,
|
|
788
|
-
timeoutMs:
|
|
793
|
+
timeoutMs: planTimeoutMs,
|
|
789
794
|
grounding: grounding.kind,
|
|
790
795
|
...(input.parentPlanId ? { parentPlanId: input.parentPlanId } : {}),
|
|
791
796
|
seedChars: {
|
|
@@ -815,7 +820,7 @@ export function createGeneratePlanTool(deps) {
|
|
|
815
820
|
};
|
|
816
821
|
// Merge the outer abort signal with a wall-clock timeout — either
|
|
817
822
|
// cancels the planner promptly.
|
|
818
|
-
const signal = AbortSignal.any([ctx.signal, AbortSignal.timeout(
|
|
823
|
+
const signal = AbortSignal.any([ctx.signal, AbortSignal.timeout(planTimeoutMs)]);
|
|
819
824
|
let runError = null;
|
|
820
825
|
let run = null;
|
|
821
826
|
try {
|
|
@@ -98,6 +98,10 @@ export function createInspectDataProfileTool(pool) {
|
|
|
98
98
|
.optional()
|
|
99
99
|
.describe(`Per-file records per page. Only used when scope is 'files'. Default ${DEFAULT_PAGE_SIZE}, max ${MAX_PAGE_SIZE}.`),
|
|
100
100
|
}),
|
|
101
|
+
// `execute` defaults both fields internally, and the hook applies the same
|
|
102
|
+
// defaults. Without them the ordinary call, which names no field at all,
|
|
103
|
+
// gives no detail. `page` acts only on the `files` scope, as in `execute`.
|
|
104
|
+
describeCall: ({ scope, page }) => ((scope ?? "overview") === "files" ? `files (page ${page ?? 1})` : "overview"),
|
|
101
105
|
execute: async (input, ctx) => {
|
|
102
106
|
const resourceId = scopeResource(ctx.session.scope).resourceId;
|
|
103
107
|
const status = unwrapOrThrow(await loadDataProfileStatus(pool, resourceId));
|
|
@@ -68,6 +68,7 @@ export function createLiteratureReviewerTool(deps) {
|
|
|
68
68
|
"biological features to investigate and the experimental " +
|
|
69
69
|
"context. The reviewer sees only this — include everything it needs."),
|
|
70
70
|
}),
|
|
71
|
+
describeCall: "none",
|
|
71
72
|
execute: async ({ brief }, ctx) => {
|
|
72
73
|
const { messages: transcript } = await runAgent(agent, [{ role: "user", content: brief }], forSubAgent(ctx.session, AGENT_ID), {
|
|
73
74
|
provider: deps.provider,
|
|
@@ -18,6 +18,7 @@ export const searchArxivTool = defineTool({
|
|
|
18
18
|
.describe('Optional arXiv category filters (e.g., ["cs.LG", "math.OC"]). ' + "Combined with the query using AND."),
|
|
19
19
|
limit: z.number().int().min(1).max(20).default(10).describe("Maximum results (1–20, default 10)."),
|
|
20
20
|
}),
|
|
21
|
+
describeCall: "none",
|
|
21
22
|
execute: async ({ query, categories, limit }, context) => {
|
|
22
23
|
const result = await source.search({ query, ...(categories === undefined ? {} : { categories }), limit }, context.signal);
|
|
23
24
|
if (result.status !== "ok")
|
|
@@ -61,6 +61,7 @@ export function createSearchGithubReposTool(deps) {
|
|
|
61
61
|
language: z.string().optional().describe('Optional language filter (e.g., "python").'),
|
|
62
62
|
limit: z.number().int().min(1).max(20).default(10).describe("Maximum results (1–20, default 10)."),
|
|
63
63
|
}),
|
|
64
|
+
describeCall: "none",
|
|
64
65
|
execute: async ({ query, language, limit }) => {
|
|
65
66
|
const q = language ? `${query} language:${language}` : query;
|
|
66
67
|
const params = new URLSearchParams({
|
|
@@ -24,6 +24,7 @@ export function createSearchSemanticScholarTool(options = {}) {
|
|
|
24
24
|
'"reinforcement learning for control" or "feedback stabilisation in oscillators".'),
|
|
25
25
|
limit: z.number().int().min(1).max(20).default(10).describe("Maximum number of results to return (1–20, default 10)."),
|
|
26
26
|
}),
|
|
27
|
+
describeCall: "none",
|
|
27
28
|
execute: async ({ query, limit }, context) => {
|
|
28
29
|
const result = await source.search(query, limit, context.signal);
|
|
29
30
|
if (result.status !== "ok")
|
|
@@ -26,6 +26,7 @@ import { readFile } from "node:fs/promises";
|
|
|
26
26
|
import { ok } from "neverthrow";
|
|
27
27
|
import { z } from "zod";
|
|
28
28
|
import { defineTool } from "../define-tool.js";
|
|
29
|
+
import { capCodePoints, DETAIL_NEEDLE_MAX_LENGTH } from "../../loop/tool-detail.js";
|
|
29
30
|
/**
|
|
30
31
|
* Where the list lives when the host mounts the library store at the same path
|
|
31
32
|
* the sandbox does. Hosts that do not — the store is baked into the image and
|
|
@@ -187,6 +188,28 @@ export function createListAvailablePackagesTool(deps = {}) {
|
|
|
187
188
|
.optional()
|
|
188
189
|
.describe(`Cap the packages listed. Omit to get the whole set — the store fits well within the ${DEFAULT_LIMIT} ceiling. Ignored when \`names\` is given.`),
|
|
189
190
|
}),
|
|
191
|
+
// `queryPackages` returns on `names` before it reads another field, thus
|
|
192
|
+
// the presence check comes first. An empty array is not a presence check.
|
|
193
|
+
// `names` are exact identifiers a caller checks for, thus they name the
|
|
194
|
+
// call as they are. A blank query counts as absent, because `queryPackages`
|
|
195
|
+
// trims the needle and applies no filter when nothing is left.
|
|
196
|
+
//
|
|
197
|
+
// A needle rides behind `matching "…"`, because a bare needle reads as a
|
|
198
|
+
// package name. The needle takes its own bound, thus the emit-site cap
|
|
199
|
+
// cannot cut inside the mark and leave the quote open. `language` stays a
|
|
200
|
+
// trailing qualifier, exactly as `execute` treats it.
|
|
201
|
+
describeCall: ({ names, query, language }) => {
|
|
202
|
+
if (names !== undefined && names.length > 0)
|
|
203
|
+
return names.join(", ");
|
|
204
|
+
const needle = query?.trim();
|
|
205
|
+
if (needle !== undefined && needle !== "") {
|
|
206
|
+
const marked = `matching "${capCodePoints(needle, DETAIL_NEEDLE_MAX_LENGTH)}"`;
|
|
207
|
+
return language === undefined ? marked : `${marked} (${language})`;
|
|
208
|
+
}
|
|
209
|
+
if (language !== undefined)
|
|
210
|
+
return `${language} packages`;
|
|
211
|
+
return "full package list";
|
|
212
|
+
},
|
|
190
213
|
execute: async (input) => {
|
|
191
214
|
// An unreadable inventory is an expected environment state — model it as an
|
|
192
215
|
// `available: false` data variant telling the caller the set is UNKNOWN.
|
|
@@ -27,6 +27,7 @@ import { lstat, readFile, readdir } from "node:fs/promises";
|
|
|
27
27
|
import { join as hostJoin, posix as posixPath } from "node:path";
|
|
28
28
|
import { ok } from "neverthrow";
|
|
29
29
|
import { z } from "zod";
|
|
30
|
+
import { capCodePoints, DETAIL_MAX_LENGTH, DETAIL_NEEDLE_MAX_LENGTH } from "../../loop/tool-detail.js";
|
|
30
31
|
import { REFERENCE_DATA_CATALOG } from "../../reference-data/catalog.js";
|
|
31
32
|
import { parseReferenceInstallReceipt } from "../../reference-data/receipt.js";
|
|
32
33
|
import { defineTool } from "../define-tool.js";
|
|
@@ -510,6 +511,41 @@ export function createListAvailableRefsTool(deps) {
|
|
|
510
511
|
"The store is provisioned by the host and is NOT frozen: datasets can be added through the host's own provisioning path, and whatever is added shows up on a later call. " +
|
|
511
512
|
"So before you report a gap as permanent, check whether any tool you hold reaches that provisioning path — offer that route if one exists, and call the gap permanent only when none does.",
|
|
512
513
|
inputSchema: ListAvailableRefsInputSchema,
|
|
514
|
+
// `execute` resolves `path ?? category` for the subtree it inspects, and
|
|
515
|
+
// it ignores `category` when `path` is present. `query` is an ADDITIVE
|
|
516
|
+
// filter over that subtree, thus the target keeps naming the call and the
|
|
517
|
+
// filter rides beside it.
|
|
518
|
+
//
|
|
519
|
+
// A needle rides behind `matching "…"`: a bare needle reads as a path, and
|
|
520
|
+
// a filtered subtree must name both halves, not the target alone. The two
|
|
521
|
+
// halves join with a space and a word, as `grep` joins its own two halves.
|
|
522
|
+
// A separator glyph belongs to a host, which owns its line and its own
|
|
523
|
+
// vocabulary; the harness renders nothing and thus borrows nothing.
|
|
524
|
+
//
|
|
525
|
+
// Each half carries its own bound. The emit-site cap cuts the TAIL, and
|
|
526
|
+
// `path` admits 4096 bytes, thus one long target would swallow the very
|
|
527
|
+
// filter this detail exists to state. The needle takes a fixed budget, and
|
|
528
|
+
// the target takes what the bounded filter leaves. Then both halves reach
|
|
529
|
+
// a reader, and each one marks its own cut.
|
|
530
|
+
//
|
|
531
|
+
// A blank value counts as absent, because `execute` treats it that way: it
|
|
532
|
+
// browses the store root for a blank target, and it applies no filter for a
|
|
533
|
+
// blank needle. The precedence stays `??` and never `||`, thus a blank
|
|
534
|
+
// `path` still suppresses `category` exactly as `execute` does.
|
|
535
|
+
describeCall: ({ path, category, query }) => {
|
|
536
|
+
const target = path ?? category;
|
|
537
|
+
const named = target !== undefined && target.trim() !== "" ? target : undefined;
|
|
538
|
+
const needle = query?.trim();
|
|
539
|
+
const filter = needle === undefined || needle === "" ? undefined : `matching "${capCodePoints(needle, DETAIL_NEEDLE_MAX_LENGTH)}"`;
|
|
540
|
+
if (named === undefined)
|
|
541
|
+
return filter ?? "full reference store";
|
|
542
|
+
if (filter === undefined)
|
|
543
|
+
return named;
|
|
544
|
+
// The budget comes off the rendered filter, never off a count of its
|
|
545
|
+
// parts, thus it cannot drift from the template above. One code point
|
|
546
|
+
// pays for the joining space.
|
|
547
|
+
return `${capCodePoints(named, DETAIL_MAX_LENGTH - [...filter].length - 1)} ${filter}`;
|
|
548
|
+
},
|
|
513
549
|
execute: async ({ path, category, query, limit }) => {
|
|
514
550
|
// `category` is shorthand for a top-level path; an explicit `path` wins.
|
|
515
551
|
const requestedInput = path ?? category;
|
|
@@ -33,6 +33,7 @@ export function createReportBlockerToolFor(deps) {
|
|
|
33
33
|
id: "report_blocker",
|
|
34
34
|
description: `Terminal. ${deps.blockedWhen} Pass a clear reason. Stop immediately after calling — take no further actions.`,
|
|
35
35
|
inputSchema: ReportBlockerInputSchema,
|
|
36
|
+
describeCall: "none",
|
|
36
37
|
execute: async (input) => {
|
|
37
38
|
deps.record({ kind: "blocker", reason: input.reason });
|
|
38
39
|
return ok({
|
|
@@ -54,6 +54,7 @@ export function createSkillTools(deps) {
|
|
|
54
54
|
query: z.string().min(1).describe("Keywords to match (e.g. 'PyDESeq2 contrast syntax')."),
|
|
55
55
|
topK: z.number().int().min(1).max(MAX_TOP_K).optional().describe(`Max results to return. Defaults to ${DEFAULT_TOP_K}.`),
|
|
56
56
|
}),
|
|
57
|
+
describeCall: "none",
|
|
57
58
|
execute: async ({ query, topK }) => {
|
|
58
59
|
if (allowed.size === 0) {
|
|
59
60
|
return ok({ status: "no_skills" });
|
|
@@ -114,6 +115,7 @@ export function createSkillTools(deps) {
|
|
|
114
115
|
skill: z.string().min(1).describe("Skill name (one of your declared skills)."),
|
|
115
116
|
path: z.string().min(1).describe("File path within the skill, e.g. 'SKILL.md' or 'references/pydeseq2-api.md'."),
|
|
116
117
|
}),
|
|
118
|
+
describeCall: "none",
|
|
117
119
|
execute: async ({ skill, path }) => {
|
|
118
120
|
if (!allowed.has(skill)) {
|
|
119
121
|
return ok({ status: "skill_not_declared", skill });
|
|
@@ -22,6 +22,7 @@ export function createFileStatTool(fs, workingDir) {
|
|
|
22
22
|
"in bytes. Use before reading to size a file. Missing and out-of-scope " +
|
|
23
23
|
"paths return a data variant, not an error.",
|
|
24
24
|
inputSchema: FileStatInputSchema,
|
|
25
|
+
describeCall: "none",
|
|
25
26
|
execute: async ({ path }, ctx) => {
|
|
26
27
|
const result = unwrapOrThrow(await fs.stat({ session: ctx.session, path, workingDir }));
|
|
27
28
|
switch (result.kind) {
|
|
@@ -38,6 +38,8 @@ export const showFileTool = defineTool({
|
|
|
38
38
|
"Cards render in call order, so to interleave figures with prose alternate `show_user(markdown)` and `show_file` calls " +
|
|
39
39
|
"(markdown image syntax pointing at a workspace file does NOT render — this tool is the only way to show one).",
|
|
40
40
|
inputSchema: ShowFileInputSchema,
|
|
41
|
+
// `.min(1)` on `files` makes index 0 total after the parse.
|
|
42
|
+
describeCall: ({ files }) => (files.length === 1 ? files[0].path : `${files.length} files`),
|
|
41
43
|
execute: async (input, ctx) => {
|
|
42
44
|
const card = buildFileReferenceCardData(input);
|
|
43
45
|
// A malformed/traversal path yields no card — an expected outcome the model can self-correct
|
|
@@ -32,6 +32,7 @@ export function createShowPlanTool(pool) {
|
|
|
32
32
|
.describe("Plan ID (`pln-<8hex>`) from `generate_plan` (newly created) or `inspect_run` (each run carries the planId it ran)"),
|
|
33
33
|
title: z.string().optional().describe("Optional card heading; defaults to the plan's own title"),
|
|
34
34
|
}),
|
|
35
|
+
describeCall: ({ planId }) => planId,
|
|
35
36
|
execute: async ({ planId, title }, ctx) => {
|
|
36
37
|
const resourceId = scopeResource(ctx.session.scope).resourceId;
|
|
37
38
|
// "Plan not found" / "plan no longer parses" are expected outcomes —
|
|
@@ -298,8 +298,12 @@ export interface ExecuteAnalysisDeps {
|
|
|
298
298
|
* point. Idempotency is the consumer's job, achieved via the deterministic
|
|
299
299
|
* identifiers the events carry, not via step caching. Every call site is
|
|
300
300
|
* guarded so a throwing observer never fails the run.
|
|
301
|
+
*
|
|
302
|
+
* Events arrive with the run's `RunSession` from durable workflow input — a
|
|
303
|
+
* persisting realization needs its credential, scope, and identity.
|
|
304
|
+
* Implementations may ignore the parameter.
|
|
301
305
|
*/
|
|
302
|
-
readonly emitProvenance?: (event: RunProvenanceEvent) => void;
|
|
306
|
+
readonly emitProvenance?: (event: RunProvenanceEvent, session: RunSession) => void;
|
|
303
307
|
/**
|
|
304
308
|
* Optional, fire-and-forget observation of the run's live shape, for a host driving a UI.
|
|
305
309
|
*
|
|
@@ -87,12 +87,12 @@ function emitStreamPart(part) {
|
|
|
87
87
|
* swallowed, never corrupt run state — integrity enforcement lives at the
|
|
88
88
|
* host's own ledger, not here.
|
|
89
89
|
*/
|
|
90
|
-
function emitProvenanceGuarded(deps, event) {
|
|
90
|
+
function emitProvenanceGuarded(deps, event, session) {
|
|
91
91
|
const logger = (deps.logger ?? createNoopLogger()).named("executeAnalysis");
|
|
92
92
|
if (!deps.emitProvenance)
|
|
93
93
|
return;
|
|
94
94
|
try {
|
|
95
|
-
deps.emitProvenance(event);
|
|
95
|
+
deps.emitProvenance(event, session);
|
|
96
96
|
}
|
|
97
97
|
catch (err) {
|
|
98
98
|
logger.error("emitProvenance threw", { runId: event.runId, event: event.type, ...logger.errorFields(err) });
|
|
@@ -437,7 +437,7 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
437
437
|
planSummary: input.planSummary,
|
|
438
438
|
stepCount: input.steps.length,
|
|
439
439
|
atMs: startedAtMs,
|
|
440
|
-
});
|
|
440
|
+
}, input.runSession);
|
|
441
441
|
// Opening snapshot: every step pending. The scheduler's own map does not exist yet, and an
|
|
442
442
|
// empty one yields exactly that — so a host has the run's full shape before the first dispatch
|
|
443
443
|
// rather than learning the step list from whichever transition happens to land first.
|
|
@@ -930,7 +930,7 @@ async function runSchedulerLoop(args) {
|
|
|
930
930
|
status: stepStatus,
|
|
931
931
|
...(stepDurationMs !== undefined ? { durationMs: stepDurationMs } : {}),
|
|
932
932
|
atMs: settledAtMs,
|
|
933
|
-
});
|
|
933
|
+
}, input.runSession);
|
|
934
934
|
}
|
|
935
935
|
await drainBudgetExceededNotifications(budgetExceededChildIds);
|
|
936
936
|
return {
|
|
@@ -1000,7 +1000,7 @@ async function collectAndComplete(args) {
|
|
|
1000
1000
|
status,
|
|
1001
1001
|
atMs: terminalAtMs,
|
|
1002
1002
|
durationMs: terminalAtMs - startedAtMs,
|
|
1003
|
-
});
|
|
1003
|
+
}, input.runSession);
|
|
1004
1004
|
try {
|
|
1005
1005
|
await DBOS.runStep(async () => {
|
|
1006
1006
|
unwrapOrThrow(await updateRunStatus(deps.pool, runId, status, failureReason ?? (status === "canceled" && !budgetExceeded ? "external_cancel" : null)));
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Phase 0 `phase0Resolve` — only step allowed to throw
|
|
8
8
|
* (terminates → `target-unresolved`)
|
|
9
|
-
* Phase 1
|
|
9
|
+
* Phase 1 15 collectors in parallel — coverage-envelope failures
|
|
10
10
|
* Progress `emitProgress("deciding")`
|
|
11
11
|
* Phase 2 2 decisions in parallel — LLM calls, attempt-numbered
|
|
12
12
|
* Progress `emitProgress("fanning_out")`
|
|
13
13
|
* Phase 3 4 fan-out blocks in parallel — per-item HTTP, withHost semaphore
|
|
14
14
|
* Progress `emitProgress("assembling")`
|
|
15
|
-
* Phase 4 `phase4Assemble` —
|
|
15
|
+
* Phase 4 `phase4Assemble` — aggregation + cached annotator
|
|
16
|
+
* Phase 4a `ta-approval-precedents` — one openFDA lookup, two projections
|
|
17
|
+
* Phase 4b `assembleSafetyCorroboration` — pure fold, no step of its own
|
|
18
|
+
* Phase 4c `investigateClaims` — propose / critique / re-verify / converge
|
|
16
19
|
* Progress `emitProgress("synthesizing")`
|
|
17
20
|
* Phase 5a 3 per-section syntheses in parallel
|
|
18
21
|
* Phase 5b `dossierRecommendation` sequential
|
|
@@ -38,6 +41,7 @@ import type { Logger } from "../lib/logger.js";
|
|
|
38
41
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
39
42
|
import type { AgentChat } from "../providers/types.js";
|
|
40
43
|
import { TargetAssessmentInputSchema } from "./target-assessment/schemas.js";
|
|
44
|
+
import { type ClaimInvestigationConfig } from "./target-assessment/investigation/index.js";
|
|
41
45
|
import { type ProgressPhase } from "./target-assessment/progress.js";
|
|
42
46
|
export declare const ExecuteTargetAssessmentInputSchema: z.ZodObject<{
|
|
43
47
|
assessmentId: z.ZodString;
|
|
@@ -91,6 +95,13 @@ export interface ExecuteTargetAssessmentDeps {
|
|
|
91
95
|
readonly synthesisModel: string;
|
|
92
96
|
/** LLM usage-accounting seam for the assessment's agent loops; omitted falls back to the no-op recorder. */
|
|
93
97
|
readonly usageRecorder?: UsageRecorder;
|
|
98
|
+
/**
|
|
99
|
+
* Bounds the claim-investigation phase runs under. Omitted falls back to
|
|
100
|
+
* `DEFAULT_CLAIM_INVESTIGATION_CONFIG` — the values are cost and termination
|
|
101
|
+
* bounds, not domain constants, so an embedder wanting a deeper pass sets
|
|
102
|
+
* its own and the dossier reports what was in force.
|
|
103
|
+
*/
|
|
104
|
+
readonly claimInvestigation?: Partial<ClaimInvestigationConfig>;
|
|
94
105
|
}
|
|
95
106
|
/**
|
|
96
107
|
* Register the executeTargetAssessment workflow with DBOS. Caller is
|