@inflexa-ai/harness 0.48.0 → 0.48.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contracts/chat-events.d.ts +40 -0
- package/dist/contracts/chat-frame.js +11 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/message.d.ts +7 -1
- package/dist/contracts/schemas/chat-events.d.ts +319 -0
- package/dist/contracts/schemas/chat-events.js +29 -0
- package/dist/execution/run-synthesis.js +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -1
- package/dist/input-sanitization.d.ts +30 -2
- package/dist/input-sanitization.js +187 -2
- package/dist/loop/run-loop.js +50 -5
- package/dist/loop/tool-adapter.d.ts +8 -0
- package/dist/loop/tool-adapter.js +75 -4
- package/dist/loop/tool-detail.d.ts +5 -1
- package/dist/loop/tool-detail.js +7 -3
- package/dist/loop/tool-value.d.ts +57 -0
- package/dist/loop/tool-value.js +249 -0
- package/dist/loop/types.d.ts +9 -2
- package/dist/memory/conversation-display-storage.js +4 -1
- package/dist/memory/count-tokens.js +29 -2
- package/dist/runtime/dbos.d.ts +6 -0
- package/dist/runtime/dbos.js +14 -2
- package/dist/sandbox/sandbox-step-translate.d.ts +5 -9
- package/dist/sandbox/sandbox-step-translate.js +5 -9
- package/dist/state/artifacts.d.ts +4 -1
- package/dist/state/artifacts.js +4 -1
- package/dist/state/index.d.ts +2 -2
- package/dist/state/index.js +1 -1
- package/dist/state/plans.d.ts +27 -0
- package/dist/state/plans.js +57 -3
- package/dist/tasks/data-profile-activity.d.ts +3 -7
- package/dist/tasks/data-profile-activity.js +1 -1
- package/dist/tasks/data-profile.js +3 -6
- package/dist/tools/ad-hoc-router.js +1 -0
- package/dist/tools/bio/alphafold-prediction.js +1 -0
- 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/lookup-annotation.js +1 -0
- package/dist/tools/bio/opentargets.js +1 -0
- package/dist/tools/bio/pubchem.js +1 -0
- package/dist/tools/bio/pubmed.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-gene.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/search-patents.js +1 -0
- package/dist/tools/bio/search-protein.js +1 -0
- package/dist/tools/bio/search-regulatory-approvals.js +1 -0
- package/dist/tools/bio/target-safety.js +1 -0
- package/dist/tools/capability-data.js +5 -0
- package/dist/tools/define-tool.d.ts +18 -0
- package/dist/tools/define-tool.js +2 -0
- package/dist/tools/display/show-user.js +1 -0
- package/dist/tools/execute-analysis.js +4 -0
- package/dist/tools/knowledge/check.d.ts +47 -1
- package/dist/tools/knowledge/check.js +10 -1
- package/dist/tools/knowledge/client.d.ts +1 -1
- package/dist/tools/knowledge/recommend.js +4 -3
- package/dist/tools/knowledge/situation.d.ts +39 -1
- package/dist/tools/knowledge/situation.js +80 -8
- package/dist/tools/knowledge/skeleton.d.ts +7 -0
- package/dist/tools/knowledge/skeleton.js +88 -2
- package/dist/tools/knowledge/template.js +1 -0
- package/dist/tools/memory/update-working-memory.js +5 -0
- package/dist/tools/read-tool-output.js +4 -0
- package/dist/tools/report-authoring/authoring-tools.js +8 -0
- package/dist/tools/report-session/examine-page.js +5 -0
- package/dist/tools/report-session/list-artifacts.js +5 -0
- package/dist/tools/report-session/preview-report.js +1 -0
- package/dist/tools/report-session/record-version.js +1 -0
- package/dist/tools/report-session/script-tools.js +2 -0
- package/dist/tools/research/context7-docs.js +2 -0
- package/dist/tools/research/generate-analogy-report.js +3 -0
- package/dist/tools/research/generate-plan.js +6 -0
- package/dist/tools/research/inspect-data-profile.js +5 -0
- package/dist/tools/research/inspect-run.js +5 -0
- package/dist/tools/research/resolve-citation.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/research/web-fetch.js +1 -0
- package/dist/tools/research/web-search.js +1 -0
- package/dist/tools/sandbox/lease-tools.js +2 -0
- package/dist/tools/sandbox/link-packages.js +1 -0
- package/dist/tools/sandbox/list-available-packages.js +5 -0
- package/dist/tools/sandbox/list-available-refs.js +5 -0
- package/dist/tools/sandbox/report-blocker.js +1 -0
- package/dist/tools/sandbox/skills.js +2 -0
- package/dist/tools/sandbox/submit-file-metadata.js +1 -0
- package/dist/tools/script/file-tools.js +6 -0
- package/dist/tools/script/run-script.js +20 -1
- package/dist/tools/start-report-session.js +1 -0
- package/dist/tools/tool-view.d.ts +63 -0
- package/dist/tools/tool-view.js +68 -0
- package/dist/tools/workspace/edit-file.js +5 -0
- package/dist/tools/workspace/execute-command.js +5 -2
- package/dist/tools/workspace/file-stat.js +5 -0
- package/dist/tools/workspace/grep.js +4 -0
- package/dist/tools/workspace/list-files.js +5 -0
- package/dist/tools/workspace/read-file.js +4 -0
- package/dist/tools/workspace/scan-inputs.js +5 -0
- package/dist/tools/workspace/show-file.js +1 -0
- package/dist/tools/workspace/show-plan.js +1 -0
- package/dist/tools/workspace/workspace-search.js +5 -0
- package/dist/tools/workspace/write-file.js +7 -2
- package/dist/workflows/execute-analysis.js +2 -1
- package/dist/workflows/sandbox-step.js +3 -7
- package/package.json +1 -1
- package/dist/tools/detail-resolver.d.ts +0 -40
- package/dist/tools/detail-resolver.js +0 -44
|
@@ -70,6 +70,7 @@ export function createLinkPackagesTool(deps) {
|
|
|
70
70
|
"After a `collision` of one name that BOTH tracks hold, call this tool again for that package with the prefixed form, `python:<name>` or `r:<name>`. " +
|
|
71
71
|
"A collision is terminal only after that second call also refuses, or when it names two versions of one distribution — then report it and continue without the package.",
|
|
72
72
|
inputSchema: LinkPackagesInputSchema,
|
|
73
|
+
view: "none",
|
|
73
74
|
describeCall: ({ packages }) => packages.join(", "),
|
|
74
75
|
execute: async (input) => {
|
|
75
76
|
// Every entry parses before any link lands. A partial link over a
|
|
@@ -495,6 +495,11 @@ export function createListAvailablePackagesTool(deps = {}) {
|
|
|
495
495
|
"`limit`: cap the packages listed; the response always carries the true `total` and a `hasMore` flag, so truncation is never silent. " +
|
|
496
496
|
"Each entry renders as `name==version` where the source pins a version, and a `names` check also reports the store directory and the content hash where the source records them.",
|
|
497
497
|
inputSchema: ListAvailablePackagesInputSchema,
|
|
498
|
+
view: {
|
|
499
|
+
input: (input) => input,
|
|
500
|
+
result: (_input, result) => result,
|
|
501
|
+
error: true,
|
|
502
|
+
},
|
|
498
503
|
// `queryPackages` returns on `names` before it reads another field, thus
|
|
499
504
|
// the presence check comes first. An empty array is not a presence check.
|
|
500
505
|
// `names` are exact identifiers a caller checks for, thus they name the
|
|
@@ -600,6 +600,11 @@ export function createListAvailableRefsTool(deps) {
|
|
|
600
600
|
"An empty result means the dataset is absent right now — not that it is unobtainable. Say so; never guess a path, and never silently substitute a different dataset. " +
|
|
601
601
|
"The store is provisioned by the host and is NOT frozen: datasets can be added through the host's own provisioning path between runs, and whatever is added shows up in a later run. Within one run the listing does not change, thus one call per question is enough.",
|
|
602
602
|
inputSchema: ListAvailableRefsInputSchema,
|
|
603
|
+
view: {
|
|
604
|
+
input: (input) => input,
|
|
605
|
+
result: (_input, result) => result,
|
|
606
|
+
error: true,
|
|
607
|
+
},
|
|
603
608
|
// `execute` resolves `path ?? category` for the subtree it inspects, and
|
|
604
609
|
// it ignores `category` when `path` is present. `query` is an ADDITIVE
|
|
605
610
|
// filter over that subtree, thus the target keeps naming the call and the
|
|
@@ -37,6 +37,7 @@ export function createReportBlockerToolFor(deps) {
|
|
|
37
37
|
id: REPORT_BLOCKER_TOOL_ID,
|
|
38
38
|
description: `Terminal. ${deps.blockedWhen} Pass a clear reason. Stop immediately after calling — take no further actions.`,
|
|
39
39
|
inputSchema: ReportBlockerInputSchema,
|
|
40
|
+
view: "none",
|
|
40
41
|
describeCall: "none",
|
|
41
42
|
execute: (input) => Promise.resolve(ok({ kind: "blocker", reason: input.reason, message: "Blocker recorded. You are done — stop now and do not take further actions." })),
|
|
42
43
|
});
|
|
@@ -63,6 +63,7 @@ export function createSkillTools(deps) {
|
|
|
63
63
|
"and the first line that holds a query word — ranked by how many distinct query words the file " +
|
|
64
64
|
"holds. Then `skill_read` the file. No matches returns a data variant.",
|
|
65
65
|
inputSchema: SkillSearchInputSchema,
|
|
66
|
+
view: "none",
|
|
66
67
|
describeCall: "none",
|
|
67
68
|
execute: async ({ query, topK }) => {
|
|
68
69
|
if (allowed.size === 0) {
|
|
@@ -121,6 +122,7 @@ export function createSkillTools(deps) {
|
|
|
121
122
|
"found via skill_search). Confined to your declared skills. Undeclared " +
|
|
122
123
|
"skills and missing paths return a data variant.",
|
|
123
124
|
inputSchema: SkillReadInputSchema,
|
|
125
|
+
view: "none",
|
|
124
126
|
describeCall: "none",
|
|
125
127
|
execute: async ({ skill, path }) => {
|
|
126
128
|
if (!allowed.has(skill)) {
|
|
@@ -43,6 +43,7 @@ export function createSubmitFileMetadataTool(cell) {
|
|
|
43
43
|
"accepted=true means every file now has metadata — STOP. Otherwise drop " +
|
|
44
44
|
"the unknownPaths and describe the remaining files.",
|
|
45
45
|
inputSchema: SubmitFileMetadataInputSchema,
|
|
46
|
+
view: "none",
|
|
46
47
|
describeCall: "none",
|
|
47
48
|
execute: (input) => {
|
|
48
49
|
const knownPaths = cell.knownPaths;
|
|
@@ -165,6 +165,7 @@ function createReadTableTool(deps) {
|
|
|
165
165
|
"A larger table, or another format such as h5ad, is work for an analysis run.",
|
|
166
166
|
inputSchema: readTableInput,
|
|
167
167
|
outputSchema: ReadTableOutputSchema,
|
|
168
|
+
view: "none",
|
|
168
169
|
describeCall: ({ path, entry }) => (entry === undefined ? path : `${path} : ${entry}`),
|
|
169
170
|
describeResult: (_input, result) => `${String(result.rowCount)} rows`,
|
|
170
171
|
execute: async ({ path, entry, columns }, ctx) => {
|
|
@@ -201,6 +202,7 @@ function createListArchiveTool(deps) {
|
|
|
201
202
|
"`read_table` and `read_pdf` then read one of them as their `entry`.",
|
|
202
203
|
inputSchema: listArchiveInput,
|
|
203
204
|
outputSchema: ListArchiveOutputSchema,
|
|
205
|
+
view: "none",
|
|
204
206
|
describeCall: ({ path }) => path,
|
|
205
207
|
describeResult: (_input, result) => `${String(result.entries.length)} files`,
|
|
206
208
|
execute: async ({ path }, ctx) => {
|
|
@@ -266,6 +268,7 @@ export function createReadPdfTool(deps) {
|
|
|
266
268
|
"A scanned page with no text layer reads as an empty text.",
|
|
267
269
|
inputSchema: readPdfInput,
|
|
268
270
|
outputSchema: ReadPdfOutputSchema,
|
|
271
|
+
view: "none",
|
|
269
272
|
describeCall: ({ path, entry, firstPage, lastPage }) => {
|
|
270
273
|
const file = entry === undefined ? path : `${path} : ${entry}`;
|
|
271
274
|
return firstPage === undefined && lastPage === undefined ? file : `${file}, pages ${String(firstPage ?? 1)}-${String(lastPage ?? "")}`;
|
|
@@ -359,6 +362,7 @@ function createWriteDerivedTool(deps) {
|
|
|
359
362
|
"A name holds one file: a second write of the name with other content is refused.",
|
|
360
363
|
inputSchema: writeDerivedInput,
|
|
361
364
|
outputSchema: WrittenOutputSchema,
|
|
365
|
+
view: "none",
|
|
362
366
|
describeCall: ({ name }) => name,
|
|
363
367
|
describeResult: (_input, result) => result.path,
|
|
364
368
|
execute: async ({ name, content, rows }, ctx) => {
|
|
@@ -400,6 +404,7 @@ function createRenderChartTool(deps) {
|
|
|
400
404
|
"To show a chart in the chat, use `show_user`.",
|
|
401
405
|
inputSchema: renderChartInput,
|
|
402
406
|
outputSchema: WrittenOutputSchema,
|
|
407
|
+
view: "none",
|
|
403
408
|
describeCall: ({ name }) => name,
|
|
404
409
|
describeResult: (_input, result) => result.path,
|
|
405
410
|
execute: async ({ name, spec, width }, ctx) => {
|
|
@@ -447,6 +452,7 @@ function createExportPdfTool(deps) {
|
|
|
447
452
|
"Charts that `render_chart` wrote can appear in it as images. The PDF reads no URL.",
|
|
448
453
|
inputSchema: exportPdfInput,
|
|
449
454
|
outputSchema: ExportPdfOutputSchema,
|
|
455
|
+
view: "none",
|
|
450
456
|
describeCall: ({ name }) => name,
|
|
451
457
|
describeResult: (_input, result) => result.path,
|
|
452
458
|
execute: async ({ name, markdown, title }, ctx) => {
|
|
@@ -24,6 +24,7 @@ import { parseUnknownRecord, UnknownArraySchema } from "../../lib/unknown-contai
|
|
|
24
24
|
import { computeDetail, computeResultDetail } from "../../loop/tool-detail.js";
|
|
25
25
|
import { buildFileReferenceCardData, MAX_FILES } from "../../memory/card-builders.js";
|
|
26
26
|
import { defineTool, readToolCallRecord, readToolResultImages, withToolCallRecord, } from "../define-tool.js";
|
|
27
|
+
import { viewCode, viewFields, viewJson, viewText } from "../tool-view.js";
|
|
27
28
|
import { ScriptFileEffectSchema } from "./file-tools.js";
|
|
28
29
|
import { recordScriptRun } from "./metrics.js";
|
|
29
30
|
export const RUN_SCRIPT_TOOL_ID = "run_script";
|
|
@@ -141,7 +142,7 @@ async function runNestedCall(tool, ctx, state, log, args, signal) {
|
|
|
141
142
|
const invocationId = state.nextInvocationId();
|
|
142
143
|
const detail = computeDetail(tool, args, log);
|
|
143
144
|
const started = performance.now();
|
|
144
|
-
const startFrame = { type: "tool-started", source: state.source, toolUseId: invocationId, name: tool.id,
|
|
145
|
+
const startFrame = { type: "tool-started", source: state.source, toolUseId: invocationId, name: tool.id, detail };
|
|
145
146
|
state.observe(startFrame);
|
|
146
147
|
await ctx.emit(startFrame);
|
|
147
148
|
const finish = async (outcome, finishedDetail) => {
|
|
@@ -245,6 +246,7 @@ function descriptionOf(tools) {
|
|
|
245
246
|
return ("Run a JavaScript script that calls the tools below as functions, in a JavaScript VM on the host with no files and no network. " +
|
|
246
247
|
"Use it for lookups and for light work on tables: call several tools in parallel with Promise.all, filter, join, and count their results, and print only what you need. " +
|
|
247
248
|
"Only the printed output and the returned value come back to you.\n" +
|
|
249
|
+
"Each file that the script writes is already shown to the user in one card with the script.\n" +
|
|
248
250
|
"API: `await tools.<name>(args)` returns the JSON result of the tool and throws an Error on a failed call. " +
|
|
249
251
|
'`await describe("<name>")` returns the full description of a tool, with the meaning of each argument. ' +
|
|
250
252
|
"`text(value)` and `console.log` print. `return` gives a value. There are no timers, no fetch, and no modules.\n" +
|
|
@@ -273,6 +275,18 @@ function filesCard(description, writes, script) {
|
|
|
273
275
|
shown.push({ path: script.path, caption: "The script that derived these files." });
|
|
274
276
|
return buildFileReferenceCardData({ title: description ?? "Derived files", files: shown });
|
|
275
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* The shown result of a script. A failed script puts its failure first, because the failure is what the reader looks for.
|
|
280
|
+
* The nested calls and the files stay out: the activity part and the file card of the call carry them. A script that
|
|
281
|
+
* printed nothing and returned nothing gives no result.
|
|
282
|
+
*/
|
|
283
|
+
function scriptResultView(result) {
|
|
284
|
+
const output = result.output === "" ? undefined : viewText(result.output);
|
|
285
|
+
const fields = !result.ok
|
|
286
|
+
? viewFields({ error: result.error === undefined ? undefined : viewText(result.error), output })
|
|
287
|
+
: viewFields({ output, value: result.value === undefined ? undefined : viewJson(result.value) });
|
|
288
|
+
return fields.fields.length === 0 ? undefined : fields;
|
|
289
|
+
}
|
|
276
290
|
/** The `run_script` tool over the script tools of an agent. */
|
|
277
291
|
export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
278
292
|
const vm = sandboxRuntimeOf(runtime);
|
|
@@ -283,6 +297,11 @@ export function createRunScriptTool(scriptTools, runtime = {}, options = {}) {
|
|
|
283
297
|
id: RUN_SCRIPT_TOOL_ID,
|
|
284
298
|
description: descriptionOf(scriptTools),
|
|
285
299
|
inputSchema,
|
|
300
|
+
view: {
|
|
301
|
+
input: ({ code }) => viewCode("javascript", code),
|
|
302
|
+
result: (_input, result) => scriptResultView(result),
|
|
303
|
+
error: true,
|
|
304
|
+
},
|
|
286
305
|
describeCall: ({ description }) => description,
|
|
287
306
|
describeResult: ({ description }, result) => {
|
|
288
307
|
const tools = result.calls.length === 0 ? undefined : result.calls.map((call) => call.tool).join(", ");
|
|
@@ -141,6 +141,7 @@ export function createStartReportSessionTool(deps) {
|
|
|
141
141
|
"`no_browser` means that this deployment has no browser for the report agent, thus no report session can start; tell the user. " +
|
|
142
142
|
"`refused`, `parent_not_found`, `parent_not_a_conversation`, and `failed` mean that nothing started, and `detail` gives the reason where there is one.",
|
|
143
143
|
inputSchema: startReportSessionInput,
|
|
144
|
+
view: "none",
|
|
144
145
|
describeCall: "none",
|
|
145
146
|
execute: async (input, ctx) => {
|
|
146
147
|
const brief = {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The view of a tool: what a call of the tool shows to a host.
|
|
3
|
+
*
|
|
4
|
+
* A tool declares a `ToolCallView`, or `"none"`, in `defineTool`. A member gives the value to show, and the emit site of
|
|
5
|
+
* the loop (`loop/tool-value.ts`) cleans it: control characters out, each listed secret form out, then the cap. A member
|
|
6
|
+
* gives a string for a `text` value, any other plain value for a `json` value, or the result of one of the helpers
|
|
7
|
+
* below for a value of a kind that the author chooses.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ViewText {
|
|
10
|
+
readonly text: string;
|
|
11
|
+
readonly kind = "text";
|
|
12
|
+
constructor(text: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class ViewJson {
|
|
15
|
+
readonly value: unknown;
|
|
16
|
+
readonly kind = "json";
|
|
17
|
+
constructor(value: unknown);
|
|
18
|
+
}
|
|
19
|
+
export declare class ViewCode {
|
|
20
|
+
readonly language: string;
|
|
21
|
+
readonly text: string;
|
|
22
|
+
readonly kind = "code";
|
|
23
|
+
constructor(language: string, text: string);
|
|
24
|
+
}
|
|
25
|
+
/** A value of one kind that holds no other value. */
|
|
26
|
+
export type ViewLeaf = ViewText | ViewJson | ViewCode;
|
|
27
|
+
export declare class ViewFields {
|
|
28
|
+
readonly fields: ReadonlyArray<{
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly leaf: ViewLeaf;
|
|
31
|
+
}>;
|
|
32
|
+
readonly kind = "fields";
|
|
33
|
+
constructor(fields: ReadonlyArray<{
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly leaf: ViewLeaf;
|
|
36
|
+
}>);
|
|
37
|
+
}
|
|
38
|
+
export type ViewValue = ViewLeaf | ViewFields;
|
|
39
|
+
/**
|
|
40
|
+
* What a tool shows of its calls. Each member is synchronous and pure, and it never reaches the model. A member that is
|
|
41
|
+
* absent shows nothing. A member that throws, or that gives a value that breaks a rule of the kinds, shows nothing, and
|
|
42
|
+
* the call runs as before.
|
|
43
|
+
*/
|
|
44
|
+
export type ToolCallView<Input, Output> = {
|
|
45
|
+
/** The shown input. It reads the input after the parse with the input schema of the tool. */
|
|
46
|
+
input?(input: Input): unknown;
|
|
47
|
+
/** The shown result of a call that ended `ok`. It reads the ok value that the tool returned. */
|
|
48
|
+
result?(input: Input, result: Output): unknown;
|
|
49
|
+
/** Show the error text of a call that ended `error`. */
|
|
50
|
+
readonly error?: true;
|
|
51
|
+
};
|
|
52
|
+
/** A plain text. */
|
|
53
|
+
export declare function viewText(text: string): ViewText;
|
|
54
|
+
/** The JSON text of a value. A string shows as a JSON string, with its quotes. */
|
|
55
|
+
export declare function viewJson(value: unknown): ViewJson;
|
|
56
|
+
/** Source code. `language` has 1 to 20 characters of lowercase letters, digits, `+`, `#`, `.`, and `-`. */
|
|
57
|
+
export declare function viewCode(language: string, text: string): ViewCode;
|
|
58
|
+
/**
|
|
59
|
+
* An ordered list of named values, at most 4. A name has 1 to 40 characters of letters, digits, `_`, and `-`. A field
|
|
60
|
+
* whose leaf is `undefined` is left out. The order is the order of the keys, except that a name made of digits only
|
|
61
|
+
* goes first, as a JavaScript object orders such a key.
|
|
62
|
+
*/
|
|
63
|
+
export declare function viewFields(leaves: Readonly<Record<string, ViewLeaf | undefined>>): ViewFields;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The view of a tool: what a call of the tool shows to a host.
|
|
3
|
+
*
|
|
4
|
+
* A tool declares a `ToolCallView`, or `"none"`, in `defineTool`. A member gives the value to show, and the emit site of
|
|
5
|
+
* the loop (`loop/tool-value.ts`) cleans it: control characters out, each listed secret form out, then the cap. A member
|
|
6
|
+
* gives a string for a `text` value, any other plain value for a `json` value, or the result of one of the helpers
|
|
7
|
+
* below for a value of a kind that the author chooses.
|
|
8
|
+
*/
|
|
9
|
+
/*
|
|
10
|
+
* Each helper returns an instance of one of these classes, and the emit site tests the class with `instanceof`. A tool
|
|
11
|
+
* result with a field named `kind` is a plain object, thus it is never read as a kind. The constructors stay out of the
|
|
12
|
+
* public barrel, thus only the helpers make an instance.
|
|
13
|
+
*/
|
|
14
|
+
export class ViewText {
|
|
15
|
+
text;
|
|
16
|
+
kind = "text";
|
|
17
|
+
constructor(text) {
|
|
18
|
+
this.text = text;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class ViewJson {
|
|
22
|
+
value;
|
|
23
|
+
kind = "json";
|
|
24
|
+
constructor(value) {
|
|
25
|
+
this.value = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class ViewCode {
|
|
29
|
+
language;
|
|
30
|
+
text;
|
|
31
|
+
kind = "code";
|
|
32
|
+
constructor(language, text) {
|
|
33
|
+
this.language = language;
|
|
34
|
+
this.text = text;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class ViewFields {
|
|
38
|
+
fields;
|
|
39
|
+
kind = "fields";
|
|
40
|
+
constructor(fields) {
|
|
41
|
+
this.fields = fields;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** A plain text. */
|
|
45
|
+
export function viewText(text) {
|
|
46
|
+
return new ViewText(text);
|
|
47
|
+
}
|
|
48
|
+
/** The JSON text of a value. A string shows as a JSON string, with its quotes. */
|
|
49
|
+
export function viewJson(value) {
|
|
50
|
+
return new ViewJson(value);
|
|
51
|
+
}
|
|
52
|
+
/** Source code. `language` has 1 to 20 characters of lowercase letters, digits, `+`, `#`, `.`, and `-`. */
|
|
53
|
+
export function viewCode(language, text) {
|
|
54
|
+
return new ViewCode(language, text);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* An ordered list of named values, at most 4. A name has 1 to 40 characters of letters, digits, `_`, and `-`. A field
|
|
58
|
+
* whose leaf is `undefined` is left out. The order is the order of the keys, except that a name made of digits only
|
|
59
|
+
* goes first, as a JavaScript object orders such a key.
|
|
60
|
+
*/
|
|
61
|
+
export function viewFields(leaves) {
|
|
62
|
+
const fields = [];
|
|
63
|
+
for (const [name, leaf] of Object.entries(leaves)) {
|
|
64
|
+
if (leaf !== undefined)
|
|
65
|
+
fields.push({ name, leaf });
|
|
66
|
+
}
|
|
67
|
+
return new ViewFields(fields);
|
|
68
|
+
}
|
|
@@ -153,6 +153,11 @@ export function createEditFileTool(deps) {
|
|
|
153
153
|
"`invalid_pattern` / `invalid_arguments` data variants — never throws on expected outcomes. " +
|
|
154
154
|
"A data variant other than `ok` writes nothing.",
|
|
155
155
|
inputSchema: EditFileInputSchema,
|
|
156
|
+
view: {
|
|
157
|
+
input: (input) => input,
|
|
158
|
+
result: (_input, result) => result,
|
|
159
|
+
error: true,
|
|
160
|
+
},
|
|
156
161
|
// The path only. The replaced text is file content, not a description.
|
|
157
162
|
describeCall: ({ path }) => path,
|
|
158
163
|
execute: async ({ path, old_string, new_string, replace_all: replaceAll, regex: useRegex, expected_matches }, ctx) => {
|
|
@@ -75,6 +75,7 @@ export function createExecuteCommandTool(deps) {
|
|
|
75
75
|
"so use them to confirm that a command ran. A result that you compute with 'python -c' or an inline " +
|
|
76
76
|
"one-liner and read from stdout is lost. Write a script and persist its outputs to output/.",
|
|
77
77
|
inputSchema: ExecuteCommandInputSchema,
|
|
78
|
+
view: "none",
|
|
78
79
|
// The script is what a reader recognises, and it survives a long
|
|
79
80
|
// invocation whose flags would otherwise push it past the length cap.
|
|
80
81
|
// When nothing in the argv looks like a script, the joined argv is the
|
|
@@ -83,8 +84,10 @@ export function createExecuteCommandTool(deps) {
|
|
|
83
84
|
// `env` never rides along. It is the channel this tool documents for
|
|
84
85
|
// secrets, so it is the one field guaranteed to hold them. An argv can
|
|
85
86
|
// hold one too (`--password …` is a real habit), and the emit-site
|
|
86
|
-
// redaction only
|
|
87
|
-
// is a deliberate trade, not a claim of safety.
|
|
87
|
+
// redaction removes only the secret forms that it lists, so the join
|
|
88
|
+
// is a deliberate trade, not a claim of safety. The view is `"none"` for
|
|
89
|
+
// the same reason: the argv, the env, and the output of a command show
|
|
90
|
+
// no value.
|
|
88
91
|
//
|
|
89
92
|
// Know what the trade costs. This tool is sandbox-gated, so the only
|
|
90
93
|
// surface that reads its detail is a workflow's live-activity line — and
|
|
@@ -22,6 +22,11 @@ export function createFileStatTool(fs, workingDir) {
|
|
|
22
22
|
"in bytes. It reads the workspace directly and runs no sandbox command. 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
|
+
view: {
|
|
26
|
+
input: (input) => input,
|
|
27
|
+
result: (_input, result) => result,
|
|
28
|
+
error: true,
|
|
29
|
+
},
|
|
25
30
|
describeCall: "none",
|
|
26
31
|
execute: async ({ path }, ctx) => {
|
|
27
32
|
const result = unwrapOrThrow(await fs.stat({ session: ctx.session, path, workingDir }));
|
|
@@ -45,6 +45,10 @@ export function createGrepTool(fs, workingDir, lineage) {
|
|
|
45
45
|
"in its first KiB is skipped as binary. A `truncated` status means the match cap or the file cap cut the search; " +
|
|
46
46
|
"narrow your path or pattern. No matches, missing paths, and out-of-scope paths return data variants, not errors.",
|
|
47
47
|
inputSchema: GrepInputSchema,
|
|
48
|
+
view: {
|
|
49
|
+
input: (input) => input,
|
|
50
|
+
error: true,
|
|
51
|
+
},
|
|
48
52
|
// Both fields, because either alone loses the call: the same pattern over
|
|
49
53
|
// two trees and two patterns over one tree are both ordinary sequences.
|
|
50
54
|
describeCall: ({ pattern, path }) => `${pattern} in ${path}`,
|
|
@@ -24,6 +24,11 @@ export function createListFilesTool(fs, workingDir) {
|
|
|
24
24
|
"else: it never reaches a directory outside the analysis, including the host process's current " +
|
|
25
25
|
"working directory. Missing and out-of-scope paths return a data variant, not an error.",
|
|
26
26
|
inputSchema: ListFilesInputSchema,
|
|
27
|
+
view: {
|
|
28
|
+
input: (input) => input,
|
|
29
|
+
result: (_input, result) => result,
|
|
30
|
+
error: true,
|
|
31
|
+
},
|
|
27
32
|
// The directory IS the call — two listings differ by nothing else.
|
|
28
33
|
describeCall: ({ path }) => path,
|
|
29
34
|
execute: async ({ path }, ctx) => {
|
|
@@ -69,6 +69,10 @@ export function createReadFileTool(fs, workingDir, lineage) {
|
|
|
69
69
|
"back as a `binary` data variant rather than decoded garbage. Missing " +
|
|
70
70
|
"paths and paths outside the analysis tree return a data variant — not an error.",
|
|
71
71
|
inputSchema: ReadFileInputSchema,
|
|
72
|
+
view: {
|
|
73
|
+
input: (input) => input,
|
|
74
|
+
error: true,
|
|
75
|
+
},
|
|
72
76
|
describeCall: ({ path, headLines, tailLines }) => {
|
|
73
77
|
if (headLines !== undefined)
|
|
74
78
|
return `${path} (first ${headLines} lines)`;
|
|
@@ -44,6 +44,11 @@ export function createScanInputsTool(deps) {
|
|
|
44
44
|
"or to check a grouping you are unsure about. " +
|
|
45
45
|
"It reports observations only: which files are one KIND of data, and what a varying position MEANS, are yours to decide.",
|
|
46
46
|
inputSchema: ScanInputsInputSchema,
|
|
47
|
+
view: {
|
|
48
|
+
input: (input) => input,
|
|
49
|
+
result: (_input, result) => result,
|
|
50
|
+
error: true,
|
|
51
|
+
},
|
|
47
52
|
describeCall: ({ path }) => path,
|
|
48
53
|
execute: async ({ path }, ctx) => {
|
|
49
54
|
const scan = await scanInputTree({
|
|
@@ -42,6 +42,7 @@ export const showFileTool = defineTool({
|
|
|
42
42
|
"calls in one batch, in reading order: a section, then the `show_file` for its figures, then the next section " +
|
|
43
43
|
"(markdown image syntax pointing at a workspace file does NOT render — this tool is the only way to show one).",
|
|
44
44
|
inputSchema: ShowFileInputSchema,
|
|
45
|
+
view: "none",
|
|
45
46
|
// `.min(1)` on `files` makes index 0 total after the parse.
|
|
46
47
|
describeCall: ({ files }) => (files.length === 1 ? presentFirst(files, "the shown file").path : `${files.length} files`),
|
|
47
48
|
execute: async (input, ctx) => {
|
|
@@ -34,6 +34,7 @@ export function createShowPlanTool(pool) {
|
|
|
34
34
|
"card — that renders a second, divergent copy of the same plan. " +
|
|
35
35
|
"`shown: false` with `plan_not_found` means that the planId is no longer valid: generate a new plan, because a retry of the same planId fails the same way.",
|
|
36
36
|
inputSchema: ShowPlanInputSchema,
|
|
37
|
+
view: "none",
|
|
37
38
|
describeCall: ({ planId }) => planId,
|
|
38
39
|
execute: async ({ planId, title }, ctx) => {
|
|
39
40
|
const resourceId = scopeResource(ctx.session.scope).resourceId;
|
|
@@ -89,6 +89,11 @@ export function createWorkspaceSearchTool(pool, embedding) {
|
|
|
89
89
|
"its members. Most input files carry no entry of their own — find them through their " +
|
|
90
90
|
"group and the listing tools.",
|
|
91
91
|
inputSchema: WorkspaceSearchInputSchema,
|
|
92
|
+
view: {
|
|
93
|
+
input: (input) => input,
|
|
94
|
+
result: (_input, result) => result,
|
|
95
|
+
error: true,
|
|
96
|
+
},
|
|
92
97
|
describeCall: ({ query, type }) => (type === undefined ? query : `${query} (${type})`),
|
|
93
98
|
execute: async ({ query, type, limit }, ctx) => {
|
|
94
99
|
const [vector] = unwrapOrThrow(await embedding.embed([query], ctx.session));
|
|
@@ -30,8 +30,13 @@ export function createWriteFileTool(deps) {
|
|
|
30
30
|
"`out_of_scope`, and a symbolic link on the path returns `symlink_denied`. A path under the staged " +
|
|
31
31
|
"inputs returns `read_only` with the reason.",
|
|
32
32
|
inputSchema: WriteFileInputSchema,
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
view: {
|
|
34
|
+
input: (input) => input,
|
|
35
|
+
result: (_input, result) => result,
|
|
36
|
+
error: true,
|
|
37
|
+
},
|
|
38
|
+
// The path only: the detail is a one-line label, and `content` is a whole file. The view shows the
|
|
39
|
+
// content, and the cap of the emit site bounds it.
|
|
35
40
|
describeCall: ({ path }) => path,
|
|
36
41
|
execute: async ({ path, content }, ctx) => {
|
|
37
42
|
const write = await deps.mutator.writeFile({
|
|
@@ -82,6 +82,7 @@ import { addChatUsage, hasReportedUsage } from "../loop/metrics.js";
|
|
|
82
82
|
import { flattenDataPart } from "../contracts/chat-frame.js";
|
|
83
83
|
import { SYNTHESIS_AGENT_ID, loadStepSummariesFromDisk } from "../execution/run-synthesis.js";
|
|
84
84
|
import { MAX_UPSTREAM_ARTIFACTS, composeStepBriefing, emptyStepMemory, } from "../prompts/briefing.js";
|
|
85
|
+
import { withoutToolValues } from "../loop/tool-value.js";
|
|
85
86
|
import { SYNTHESIS_STEP_ID, runDir, runStepDir, stepSubdir, stepWritePrefix, toSandboxPath } from "../workspace/paths.js";
|
|
86
87
|
import { isChatDataPart, synthesisEmission } from "../sandbox/sandbox-step-translate.js";
|
|
87
88
|
import { synthesizeRun } from "../app/synthesize-run.js";
|
|
@@ -516,7 +517,7 @@ export function synthesizeFindings(args) {
|
|
|
516
517
|
? flattenDataPart(event)
|
|
517
518
|
: {
|
|
518
519
|
type: "data-loop-event",
|
|
519
|
-
data: { stepId: "synthesis", event },
|
|
520
|
+
data: { stepId: "synthesis", event: withoutToolValues(event) },
|
|
520
521
|
};
|
|
521
522
|
return DBOS.writeStream("events", part);
|
|
522
523
|
};
|
|
@@ -38,9 +38,9 @@ import { unwrapOrThrow } from "../lib/result.js";
|
|
|
38
38
|
import { maskExcept } from "../loop/call-policy.js";
|
|
39
39
|
import { runLoop, withRunScript } from "../loop/run-loop.js";
|
|
40
40
|
import { READ_TOOL_OUTPUT_TOOL_ID } from "../loop/tool-output.js";
|
|
41
|
+
import { withoutToolValues } from "../loop/tool-value.js";
|
|
41
42
|
import { lastExecOutcome } from "../sandbox/exec-outcome.js";
|
|
42
43
|
import { activityEmission, activityForTool, applyTreeDelta, isChatDataPart, sandboxTreeDelta, stepPartId, toolActivityKey, } from "../sandbox/sandbox-step-translate.js";
|
|
43
|
-
import { createDetailResolver } from "../tools/detail-resolver.js";
|
|
44
44
|
import { forSubAgent } from "../auth/types.js";
|
|
45
45
|
import { walkStepArtifacts } from "../execution/post-step.js";
|
|
46
46
|
import { collectStepOutputs, describeStepRegistrationFailure, generateStepFileMetadata, generateStepSummaryAndWrite, reconcileAndRegisterStepArtifacts, vectorIndexStepOutputs, } from "../execution/post-step-pipeline.js";
|
|
@@ -196,11 +196,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
196
196
|
if (deps.toolOutputStore !== undefined && !agent.tools.some((tool) => tool.id === READ_TOOL_OUTPUT_TOOL_ID)) {
|
|
197
197
|
logger.warn("the step agent declares no read_tool_output, thus its loops keep no text of a cut tool result");
|
|
198
198
|
}
|
|
199
|
-
// Built over THIS agent's tools, not a shared table: a sandbox agent's roster
|
|
200
|
-
// differs per agent type, and each tool describes its own calls.
|
|
201
|
-
// The declared tools and `run_script` describe the calls of the agent, and its script tools describe the nested calls.
|
|
202
199
|
const declared = withRunScript(agent).tools;
|
|
203
|
-
const resolveDetail = createDetailResolver([...declared, ...(agent.scriptTools ?? [])], logger);
|
|
204
200
|
// Forward every loop event onto the child's own DBOS `"events"` stream
|
|
205
201
|
// (the route fans-in parent + every active child stream — Pattern A).
|
|
206
202
|
// Pre-shaped chat data parts (e.g. `data-sandbox-event` from `run-exec.ts`)
|
|
@@ -306,9 +302,9 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
306
302
|
}
|
|
307
303
|
// Forward the raw orchestration / model-delta event under the loop-event
|
|
308
304
|
// envelope, and surface each tool call as live step activity.
|
|
309
|
-
await safeEmit({ type: "data-loop-event", data: { stepId: input.stepId, event } });
|
|
305
|
+
await safeEmit({ type: "data-loop-event", data: { stepId: input.stepId, event: withoutToolValues(event) } });
|
|
310
306
|
if (event.type === "tool-started") {
|
|
311
|
-
await emitActivity("executing", activityForTool(event.name, event.
|
|
307
|
+
await emitActivity("executing", activityForTool(event.name, event.detail), event.toolUseId);
|
|
312
308
|
}
|
|
313
309
|
};
|
|
314
310
|
const initial = [{ role: "user", content: input.prompt }];
|
package/package.json
CHANGED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The name-plus-input detail resolver.
|
|
3
|
-
*
|
|
4
|
-
* The live loop holds the resolved `Tool` at dispatch and needs nothing. Every
|
|
5
|
-
* other surface — the reload converter, a workflow's live-activity line — has a
|
|
6
|
-
* tool NAME and a persisted input, so it needs a way back to the tool. This is
|
|
7
|
-
* that way, and it is deliberately built over a tool list the CALLER supplies:
|
|
8
|
-
*
|
|
9
|
-
* - A name map internal to the harness could never see an embedder's tools. A
|
|
10
|
-
* host contributes tools through the host-tools seam, and those calls are as
|
|
11
|
-
* ambiguous in a transcript as any other.
|
|
12
|
-
* - Two harness tools legitimately share one id (`write_file` exists in both the
|
|
13
|
-
* workspace surface and the report version store). A resolver built from one
|
|
14
|
-
* agent's list cannot hold both, because a tool list rejects a duplicate id at
|
|
15
|
-
* registry construction.
|
|
16
|
-
*
|
|
17
|
-
* It resolves through the same `computeDetail` the loop uses, so a reloaded
|
|
18
|
-
* thread shows the same CALL detail the live turn showed.
|
|
19
|
-
*
|
|
20
|
-
* The bound is the input. A reload reconstructs from the persisted call alone, and a
|
|
21
|
-
* tool that describes its own RESULT names a fact that the input cannot carry — a page
|
|
22
|
-
* path, a recorded version. That line reaches the live surface on the finished event and
|
|
23
|
-
* it does not reappear here, thus a reloaded call falls back to its call detail. Nothing
|
|
24
|
-
* is persisted: storage stays the pure model transcript.
|
|
25
|
-
*/
|
|
26
|
-
import { type ToolCallDetail } from "../loop/tool-detail.js";
|
|
27
|
-
import type { Logger } from "../lib/logger.js";
|
|
28
|
-
import type { Tool } from "./define-tool.js";
|
|
29
|
-
/**
|
|
30
|
-
* Maps a tool name plus a call's input to that call's detail. Yields
|
|
31
|
-
* `undefined` for an unknown tool, a tool with no hook, and an input the tool's
|
|
32
|
-
* schema rejects.
|
|
33
|
-
*/
|
|
34
|
-
export type ToolDetailResolver = (toolName: string, input: unknown) => ToolCallDetail | undefined;
|
|
35
|
-
/**
|
|
36
|
-
* Build a resolver over `tools`. A duplicate id resolves to the first tool
|
|
37
|
-
* declaring it; callers pass one agent's composed list, which a registry has
|
|
38
|
-
* already rejected duplicates from.
|
|
39
|
-
*/
|
|
40
|
-
export declare function createDetailResolver(tools: readonly Tool[], logger?: Logger): ToolDetailResolver;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The name-plus-input detail resolver.
|
|
3
|
-
*
|
|
4
|
-
* The live loop holds the resolved `Tool` at dispatch and needs nothing. Every
|
|
5
|
-
* other surface — the reload converter, a workflow's live-activity line — has a
|
|
6
|
-
* tool NAME and a persisted input, so it needs a way back to the tool. This is
|
|
7
|
-
* that way, and it is deliberately built over a tool list the CALLER supplies:
|
|
8
|
-
*
|
|
9
|
-
* - A name map internal to the harness could never see an embedder's tools. A
|
|
10
|
-
* host contributes tools through the host-tools seam, and those calls are as
|
|
11
|
-
* ambiguous in a transcript as any other.
|
|
12
|
-
* - Two harness tools legitimately share one id (`write_file` exists in both the
|
|
13
|
-
* workspace surface and the report version store). A resolver built from one
|
|
14
|
-
* agent's list cannot hold both, because a tool list rejects a duplicate id at
|
|
15
|
-
* registry construction.
|
|
16
|
-
*
|
|
17
|
-
* It resolves through the same `computeDetail` the loop uses, so a reloaded
|
|
18
|
-
* thread shows the same CALL detail the live turn showed.
|
|
19
|
-
*
|
|
20
|
-
* The bound is the input. A reload reconstructs from the persisted call alone, and a
|
|
21
|
-
* tool that describes its own RESULT names a fact that the input cannot carry — a page
|
|
22
|
-
* path, a recorded version. That line reaches the live surface on the finished event and
|
|
23
|
-
* it does not reappear here, thus a reloaded call falls back to its call detail. Nothing
|
|
24
|
-
* is persisted: storage stays the pure model transcript.
|
|
25
|
-
*/
|
|
26
|
-
import { computeDetail } from "../loop/tool-detail.js";
|
|
27
|
-
import { createNoopLogger } from "../lib/console-logger.js";
|
|
28
|
-
/**
|
|
29
|
-
* Build a resolver over `tools`. A duplicate id resolves to the first tool
|
|
30
|
-
* declaring it; callers pass one agent's composed list, which a registry has
|
|
31
|
-
* already rejected duplicates from.
|
|
32
|
-
*/
|
|
33
|
-
export function createDetailResolver(tools, logger) {
|
|
34
|
-
const log = (logger ?? createNoopLogger()).named("tool-detail");
|
|
35
|
-
const byId = new Map();
|
|
36
|
-
for (const tool of tools) {
|
|
37
|
-
if (!byId.has(tool.id))
|
|
38
|
-
byId.set(tool.id, tool);
|
|
39
|
-
}
|
|
40
|
-
return (toolName, input) => {
|
|
41
|
-
const tool = byId.get(toolName);
|
|
42
|
-
return tool === undefined ? undefined : computeDetail(tool, input, log);
|
|
43
|
-
};
|
|
44
|
-
}
|