@inflexa-ai/harness 0.44.0 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/conversation-agent.d.ts +10 -0
- package/dist/agents/conversation-agent.js +19 -1
- package/dist/agents/sandbox/shared.d.ts +23 -2
- package/dist/agents/sandbox/shared.js +51 -18
- package/dist/agents/sandbox/translational-safety-agent.js +26 -0
- package/dist/agents/sandbox/types.d.ts +24 -1
- package/dist/agents/sandbox-catalog.d.ts +14 -0
- package/dist/agents/sandbox-catalog.js +19 -0
- package/dist/app/approve-plan.d.ts +51 -0
- package/dist/app/approve-plan.js +84 -0
- package/dist/app/chat-turn.js +6 -3
- package/dist/app/data-profile-view.d.ts +1 -1
- package/dist/app/run-activity.js +4 -6
- package/dist/config/environment-stores.d.ts +4 -2
- package/dist/contracts/chat-events.d.ts +14 -2
- package/dist/contracts/chat-events.js +1 -1
- package/dist/contracts/chat-frame.d.ts +7 -0
- package/dist/contracts/chat-frame.js +24 -6
- package/dist/contracts/chat-parts.d.ts +73 -2
- package/dist/contracts/chat-parts.js +3 -1
- package/dist/contracts/index.d.ts +4 -2
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/part-registry.js +3 -0
- package/dist/contracts/schemas/chat-events.d.ts +38 -0
- package/dist/contracts/schemas/chat-events.js +8 -0
- package/dist/contracts/schemas/chat-parts.d.ts +239 -15
- package/dist/contracts/schemas/chat-parts.js +80 -12
- package/dist/contracts/schemas/index.d.ts +2 -2
- package/dist/contracts/schemas/index.js +2 -2
- package/dist/contracts/subagent-activity.d.ts +12 -0
- package/dist/contracts/subagent-activity.js +76 -0
- package/dist/execution/artifact-metadata.js +1 -3
- package/dist/execution/dbos-workflow-purger.d.ts +2 -0
- package/dist/execution/dbos-workflow-purger.js +21 -14
- package/dist/execution/run-canceler.d.ts +33 -11
- package/dist/execution/run-canceler.js +130 -28
- package/dist/execution/run-end-record.d.ts +30 -0
- package/dist/execution/run-end-record.js +27 -0
- package/dist/execution/run-event-parts.d.ts +6 -5
- package/dist/execution/run-event-parts.js +6 -5
- package/dist/execution/run-event-stream.d.ts +18 -6
- package/dist/execution/run-event-stream.js +67 -17
- package/dist/execution/run-synthesis.d.ts +21 -0
- package/dist/execution/run-synthesis.js +77 -3
- package/dist/index.d.ts +28 -10
- package/dist/index.js +26 -2
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/metrics.d.ts +2 -2
- package/dist/loop/call-guard.d.ts +50 -0
- package/dist/loop/call-guard.js +90 -0
- package/dist/loop/compaction.d.ts +5 -0
- package/dist/loop/compaction.js +17 -0
- package/dist/loop/run-agent.d.ts +9 -0
- package/dist/loop/run-agent.js +16 -1
- package/dist/loop/run-to-terminal.js +4 -1
- package/dist/loop/types.d.ts +8 -0
- package/dist/memory/card-builders.d.ts +11 -0
- package/dist/memory/card-builders.js +35 -21
- package/dist/memory/conversation-display-recorder.d.ts +2 -2
- package/dist/memory/conversation-display-recorder.js +38 -4
- package/dist/memory/conversation-display-storage.d.ts +9 -2
- package/dist/memory/conversation-display-storage.js +18 -3
- package/dist/memory/thread-history.d.ts +42 -8
- package/dist/memory/thread-history.js +164 -45
- package/dist/memory/thread-store.d.ts +13 -0
- package/dist/memory/thread-store.js +15 -1
- package/dist/prompts/briefing.d.ts +57 -3
- package/dist/prompts/briefing.js +129 -2
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +3 -1
- package/dist/prompts/planner.js +13 -2
- package/dist/provenance/seam.d.ts +39 -20
- package/dist/provenance/seam.js +7 -5
- package/dist/providers/ai-sdk.js +93 -11
- package/dist/providers/types.d.ts +6 -0
- package/dist/reference-data/catalog.js +141 -2
- package/dist/report-model/authoring-grammar.d.ts +26 -26
- package/dist/report-model/pin-snapshot.d.ts +1 -1
- package/dist/report-model/pin-snapshot.js +2 -50
- package/dist/runtime/assemble.d.ts +8 -1
- package/dist/runtime/assemble.js +11 -0
- package/dist/runtime/boot.d.ts +3 -0
- package/dist/runtime/boot.js +10 -1
- package/dist/sandbox/image-packages.d.ts +26 -4
- package/dist/sandbox/image-packages.js +18 -4
- package/dist/sandbox/sandbox-step-translate.d.ts +23 -0
- package/dist/sandbox/sandbox-step-translate.js +24 -0
- package/dist/schemas/artifact-manifest.d.ts +1 -1
- package/dist/schemas/artifact-manifest.js +11 -9
- package/dist/schemas/data-profile-schemas.d.ts +12 -12
- package/dist/schemas/plan-schemas.d.ts +75 -21
- package/dist/schemas/plan-schemas.js +1 -1
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/schemas/workflow-state.d.ts +113 -0
- package/dist/schemas/workflow-state.js +54 -0
- package/dist/state/analyses-metrics.d.ts +79 -7
- package/dist/state/analyses-metrics.js +141 -6
- package/dist/state/artifacts.d.ts +66 -3
- package/dist/state/artifacts.js +85 -7
- package/dist/state/data-profile.d.ts +4 -2
- package/dist/state/data-profile.js +7 -5
- package/dist/state/index.d.ts +9 -8
- package/dist/state/index.js +5 -5
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.d.ts +8 -0
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.js +12 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.d.ts +8 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.js +13 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.d.ts +8 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.js +13 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts +8 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js +13 -0
- package/dist/state/migrations/index.js +8 -0
- package/dist/state/plans.d.ts +21 -0
- package/dist/state/plans.js +25 -0
- package/dist/state/runs.d.ts +79 -14
- package/dist/state/runs.js +150 -33
- package/dist/state/schema.d.ts +2 -0
- package/dist/state/step-executions.d.ts +9 -0
- package/dist/state/step-executions.js +22 -0
- package/dist/tasks/data-profile-activity.d.ts +7 -1
- package/dist/tasks/data-profile-activity.js +18 -9
- package/dist/tasks/data-profile.d.ts +15 -1
- package/dist/tasks/data-profile.js +16 -11
- package/dist/tools/ad-hoc-router.d.ts +2 -0
- package/dist/tools/ad-hoc-router.js +18 -2
- package/dist/tools/approval/gateway.js +28 -5
- package/dist/tools/capability-data.d.ts +36 -0
- package/dist/tools/capability-data.js +73 -0
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +80 -1
- package/dist/tools/execute-analysis.js +352 -110
- package/dist/tools/knowledge/check.d.ts +60 -0
- package/dist/tools/knowledge/check.js +92 -0
- package/dist/tools/knowledge/client.d.ts +382 -0
- package/dist/tools/knowledge/client.js +302 -0
- package/dist/tools/knowledge/environment.d.ts +78 -0
- package/dist/tools/knowledge/environment.js +105 -0
- package/dist/tools/knowledge/index.d.ts +49 -0
- package/dist/tools/knowledge/index.js +70 -0
- package/dist/tools/knowledge/local-slots.d.ts +62 -0
- package/dist/tools/knowledge/local-slots.js +177 -0
- package/dist/tools/knowledge/pin.d.ts +14 -0
- package/dist/tools/knowledge/pin.js +17 -0
- package/dist/tools/knowledge/recommend.d.ts +92 -0
- package/dist/tools/knowledge/recommend.js +98 -0
- package/dist/tools/knowledge/situation.d.ts +143 -0
- package/dist/tools/knowledge/situation.js +164 -0
- package/dist/tools/knowledge/skeleton.d.ts +69 -0
- package/dist/tools/knowledge/skeleton.js +202 -0
- package/dist/tools/knowledge/template.d.ts +137 -0
- package/dist/tools/knowledge/template.js +267 -0
- package/dist/tools/research/generate-plan.d.ts +23 -0
- package/dist/tools/research/generate-plan.js +203 -12
- package/dist/tools/research/guarded-address.d.ts +20 -0
- package/dist/tools/research/guarded-address.js +148 -0
- package/dist/tools/research/inspect-data-profile.d.ts +1 -1
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/tools/research/inspect-run.js +7 -7
- package/dist/tools/research/searxng-engine.d.ts +40 -0
- package/dist/tools/research/searxng-engine.js +120 -0
- package/dist/tools/research/web-fetch.d.ts +124 -0
- package/dist/tools/research/web-fetch.js +627 -0
- package/dist/tools/research/web-search.d.ts +75 -0
- package/dist/tools/research/web-search.js +112 -0
- package/dist/tools/sandbox/list-available-packages.d.ts +7 -4
- package/dist/tools/sandbox/list-available-packages.js +12 -4
- package/dist/tools/sandbox/list-available-refs.d.ts +21 -0
- package/dist/tools/sandbox/list-available-refs.js +60 -3
- package/dist/tools/sandbox/report-blocker.js +5 -1
- package/dist/tools/terminal-text.d.ts +11 -0
- package/dist/tools/terminal-text.js +19 -0
- package/dist/tools/workspace/decision-record.d.ts +81 -0
- package/dist/tools/workspace/decision-record.js +117 -0
- package/dist/tools/workspace/edit-file.d.ts +3 -1
- package/dist/tools/workspace/edit-file.js +20 -2
- package/dist/tools/workspace/execute-command.d.ts +17 -0
- package/dist/tools/workspace/execute-command.js +28 -1
- package/dist/tools/workspace/index.d.ts +1 -0
- package/dist/tools/workspace/index.js +1 -0
- package/dist/tools/workspace/mutator.d.ts +1 -1
- package/dist/workflows/execute-analysis.d.ts +62 -21
- package/dist/workflows/execute-analysis.js +307 -79
- package/dist/workflows/plan/graph.d.ts +16 -0
- package/dist/workflows/plan/graph.js +36 -0
- package/dist/workflows/sandbox-step.d.ts +12 -0
- package/dist/workflows/sandbox-step.js +14 -12
- package/package.json +5 -1
|
@@ -37,6 +37,7 @@ import type { ExtendAnalysisFarm } from "../sandbox/types.js";
|
|
|
37
37
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
38
38
|
import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
39
39
|
import { type BioToolKeys } from "../tools/bio/keys.js";
|
|
40
|
+
import type { KnowledgeClient } from "../tools/knowledge/index.js";
|
|
40
41
|
import type { EnvironmentStorePaths } from "../config/environment-stores.js";
|
|
41
42
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
42
43
|
import type { RunLauncher } from "../execution/run-launcher.js";
|
|
@@ -44,6 +45,7 @@ import type { ProvenanceSeam } from "../provenance/seam.js";
|
|
|
44
45
|
import type { Logger } from "../lib/logger.js";
|
|
45
46
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
46
47
|
import type { CitationResolver } from "../citations/types.js";
|
|
48
|
+
import { type WebSearchEngine } from "../tools/research/web-search.js";
|
|
47
49
|
/** Canonical agent id — the single source of truth. */
|
|
48
50
|
export declare const CONVERSATION_AGENT_ID: "conversation-agent";
|
|
49
51
|
/**
|
|
@@ -63,6 +65,8 @@ export interface ConversationAgentDeps extends EnvironmentStorePaths {
|
|
|
63
65
|
readonly usageRecorder?: UsageRecorder;
|
|
64
66
|
/** Adds `read_tool_output`, and each loop-driving tool gives the same store to its sub-agent. */
|
|
65
67
|
readonly toolOutputStore?: ToolOutputStore;
|
|
68
|
+
/** The web-search engine the embedder injected; set, `web_search` attaches, absent it does not. `web_fetch` attaches either way. */
|
|
69
|
+
readonly webSearch?: WebSearchEngine;
|
|
66
70
|
/** Shared host-side bibliographic verification service. */
|
|
67
71
|
readonly citationResolver: CitationResolver;
|
|
68
72
|
/** The LLM seam every loop-driving tool runs its sub-agent on. */
|
|
@@ -107,6 +111,12 @@ export interface ConversationAgentDeps extends EnvironmentStorePaths {
|
|
|
107
111
|
readonly runLauncher: RunLauncher;
|
|
108
112
|
/** API keys for the external bio/chem data sources. */
|
|
109
113
|
readonly bioKeys: BioToolKeys;
|
|
114
|
+
/**
|
|
115
|
+
* The knowledge service client, for the planner that `generate_plan` drives.
|
|
116
|
+
* Absent, the planner attaches no knowledge tool. The conversation agent
|
|
117
|
+
* itself gets `knowledge_recommend` through `hostTools`, from the embedder.
|
|
118
|
+
*/
|
|
119
|
+
readonly knowledge?: KnowledgeClient;
|
|
110
120
|
/** Skills root. No tool of the roster reads it. */
|
|
111
121
|
readonly skillsDir: string;
|
|
112
122
|
/** Headless-Chrome config for report snapshot/preview rendering. */
|
|
@@ -36,10 +36,13 @@ import { createEditFileTool, createFileStatTool, createGrepTool, createListFiles
|
|
|
36
36
|
import { showUserTool } from "../tools/display/index.js";
|
|
37
37
|
import { createUpdateWorkingMemoryTool } from "../tools/memory/index.js";
|
|
38
38
|
import { createListAvailablePackagesTool } from "../tools/sandbox/list-available-packages.js";
|
|
39
|
+
import { createInspectCapabilityDataTool } from "../tools/capability-data.js";
|
|
39
40
|
import { createListAvailableRefsTool } from "../tools/sandbox/list-available-refs.js";
|
|
40
41
|
import { createExecuteAnalysisTool } from "../tools/execute-analysis.js";
|
|
41
42
|
import { createStartReportSessionTool } from "../tools/start-report-session.js";
|
|
42
43
|
import { createResolveCitationTool } from "../tools/research/resolve-citation.js";
|
|
44
|
+
import { createWebFetchTool } from "../tools/research/web-fetch.js";
|
|
45
|
+
import { createWebSearchTool } from "../tools/research/web-search.js";
|
|
43
46
|
/** Canonical agent id — the single source of truth. */
|
|
44
47
|
export const CONVERSATION_AGENT_ID = "conversation-agent";
|
|
45
48
|
/** Runaway guard — heavy tool-driving turns need generous headroom. */
|
|
@@ -48,7 +51,7 @@ const CONVERSATION_MAX_ITERATIONS = 200;
|
|
|
48
51
|
const CONVERSATION_COMPACTION = { turnStartBudget: 150_000, budget: 200_000 };
|
|
49
52
|
/** Build the conversation `AgentDefinition` with every tool fully dep-bound. */
|
|
50
53
|
export function createConversationAgent(deps) {
|
|
51
|
-
const { provider, pool, embedding, workspaceFs, model, utilityProvider, utilityModel, executeAnalysisWorkflow, runAuthorizer, runLauncher, bioKeys, chrome, resourcePolicy, hostTools, refStorePath, farmLockFile, imagePackagesFile, readPoolInventory, usageRecorder, toolOutputStore, citationResolver, } = deps;
|
|
54
|
+
const { provider, pool, embedding, workspaceFs, model, utilityProvider, utilityModel, executeAnalysisWorkflow, runAuthorizer, runLauncher, bioKeys, chrome, resourcePolicy, hostTools, refStorePath, farmLockFile, imagePackagesFile, readPoolInventory, usageRecorder, toolOutputStore, citationResolver, webSearch, } = deps;
|
|
52
55
|
const workingMemory = createWorkingMemory(pool);
|
|
53
56
|
const ncbi = createNcbiTools(bioKeys);
|
|
54
57
|
const chemDb = createChemDbTools(bioKeys, { ...(deps.logger ? { logger: deps.logger } : {}) });
|
|
@@ -81,6 +84,11 @@ export function createConversationAgent(deps) {
|
|
|
81
84
|
// preprint has no MEDLINE record, thus PubMed alone cannot answer it.
|
|
82
85
|
createSearchSemanticScholarTool({ ...(bioKeys.semanticScholar === undefined ? {} : { apiKey: bioKeys.semanticScholar }) }),
|
|
83
86
|
searchArxivTool,
|
|
87
|
+
// The web step of research — the injected web-search engine (the CLI runs
|
|
88
|
+
// SearXNG and binds its realization), then the guarded page fetch that
|
|
89
|
+
// reads what the search found.
|
|
90
|
+
...(webSearch ? [createWebSearchTool({ engine: webSearch })] : []),
|
|
91
|
+
createWebFetchTool({ ...(toolOutputStore ? { toolOutputStore } : {}) }),
|
|
84
92
|
// ChEMBL (compounds / drug / mechanism / bioactivity / targets behind one action).
|
|
85
93
|
chemblTool,
|
|
86
94
|
// PubChem (compound / crossrefs / assays behind one action).
|
|
@@ -109,6 +117,11 @@ export function createConversationAgent(deps) {
|
|
|
109
117
|
// blind is how a user gets asked to download something already installed, or
|
|
110
118
|
// told nothing is missing when it is; this is the only tool that can tell.
|
|
111
119
|
createListAvailableRefsTool({ ...(refStorePath ? { refStorePath } : {}) }),
|
|
120
|
+
// Which datasets a specialist capability cannot run without. `list_available_refs` answers
|
|
121
|
+
// what the store HOLDS; this answers what a capability WANTS, which no prompt may name and
|
|
122
|
+
// only a skill otherwise records — and a skill is read by a step agent, not here. Without
|
|
123
|
+
// it this agent meets an absent source as a dead end and designs around it.
|
|
124
|
+
createInspectCapabilityDataTool({ ...(refStorePath ? { refStorePath } : {}) }),
|
|
112
125
|
// What the STORE holds, pool-scope where the embedder binds the reader:
|
|
113
126
|
// the ask flow of this agent marks the packages the pool does not hold,
|
|
114
127
|
// and the farm of a new analysis is empty — a farm view here would read
|
|
@@ -130,6 +143,7 @@ export function createConversationAgent(deps) {
|
|
|
130
143
|
usageRecorder,
|
|
131
144
|
...(toolOutputStore ? { toolOutputStore } : {}),
|
|
132
145
|
bioKeys,
|
|
146
|
+
...(deps.knowledge ? { knowledge: deps.knowledge } : {}),
|
|
133
147
|
...(refStorePath ? { refStorePath } : {}),
|
|
134
148
|
...(farmLockFile ? { farmLockFile } : {}),
|
|
135
149
|
...(imagePackagesFile ? { imagePackagesFile } : {}),
|
|
@@ -147,6 +161,10 @@ export function createConversationAgent(deps) {
|
|
|
147
161
|
...(farmLockFile ? { farmLockFile } : {}),
|
|
148
162
|
...(imagePackagesFile ? { imagePackagesFile } : {}),
|
|
149
163
|
...(readPoolInventory ? { readPoolInventory } : {}),
|
|
164
|
+
// The pre-launch pass resolves the declared reference data of a capability against the
|
|
165
|
+
// store. Without this path it would read the CONTAINER path, which no host holds, and
|
|
166
|
+
// refuse every launch of a plan that declares one.
|
|
167
|
+
...(refStorePath ? { refStorePath } : {}),
|
|
150
168
|
...(deps.extendAnalysisFarm ? { extendAnalysisFarm: deps.extendAnalysisFarm } : {}),
|
|
151
169
|
...(deps.logger ? { logger: deps.logger } : {}),
|
|
152
170
|
...(usageRecorder ? { usageRecorder } : {}),
|
|
@@ -31,11 +31,19 @@ import type { AgentMeta, SandboxToolName } from "./types.js";
|
|
|
31
31
|
import type { EnvironmentStorePaths } from "../../config/environment-stores.js";
|
|
32
32
|
import type { Logger } from "../../lib/logger.js";
|
|
33
33
|
import type { CitationResolver } from "../../citations/types.js";
|
|
34
|
+
import { type KnowledgeClient, type TemplateBinding } from "../../tools/knowledge/index.js";
|
|
34
35
|
/**
|
|
35
36
|
* Tools every sandbox agent receives — sandbox-environment introspection,
|
|
36
|
-
* library docs, and
|
|
37
|
-
* `meta.tools` so planner-facing metadata and the resolved tool record
|
|
37
|
+
* library docs, run inspection, and the knowledge template. Spread into each
|
|
38
|
+
* agent's `meta.tools` so planner-facing metadata and the resolved tool record
|
|
38
39
|
* stay in sync. Keep minimal — only tools every agent genuinely needs.
|
|
40
|
+
*
|
|
41
|
+
* The knowledge template is here, not on a specialist, because the template
|
|
42
|
+
* follows the step: the planner binds a template to a step from the
|
|
43
|
+
* recommendation and routes that step to whichever specialist owns its data
|
|
44
|
+
* object. The tool acts only on the template the step grounding names, thus
|
|
45
|
+
* a step without a template costs nothing, and it attaches only when the
|
|
46
|
+
* embedder binds a knowledge client (see the registry below).
|
|
39
47
|
*/
|
|
40
48
|
export declare const BASE_SANDBOX_TOOLS: readonly SandboxToolName[];
|
|
41
49
|
/** Per-step coordinates the composition root threads through every mutate tool. */
|
|
@@ -48,6 +56,12 @@ export interface SandboxStepCoords {
|
|
|
48
56
|
readonly allowedWritePrefix: string;
|
|
49
57
|
/** Absolute unix-ms deadline of each exec. */
|
|
50
58
|
readonly deadlineMs: () => number;
|
|
59
|
+
/**
|
|
60
|
+
* The plan settings bound to the template of this step, from the durable
|
|
61
|
+
* step input. Reaches `knowledge_template` alone. Absent when the step
|
|
62
|
+
* grounds on no template, or when the host bound nothing.
|
|
63
|
+
*/
|
|
64
|
+
readonly templateBinding?: TemplateBinding;
|
|
51
65
|
}
|
|
52
66
|
/** The shared dependency graph every sandbox agent draws from. */
|
|
53
67
|
export interface SandboxAgentDeps extends EnvironmentStorePaths {
|
|
@@ -95,6 +109,13 @@ export interface SandboxAgentDeps extends EnvironmentStorePaths {
|
|
|
95
109
|
* and the package-link prompt layer appends. Without it, neither exists.
|
|
96
110
|
*/
|
|
97
111
|
readonly extendAnalysisFarm?: ExtendAnalysisFarm;
|
|
112
|
+
/**
|
|
113
|
+
* The knowledge service client. Bound, an agent that declares
|
|
114
|
+
* `knowledgeTemplate` gets the tool, over the same mutator as `write_file`.
|
|
115
|
+
* Absent, the declaration resolves to nothing, which is the default state
|
|
116
|
+
* of the open-source host and never an error.
|
|
117
|
+
*/
|
|
118
|
+
readonly knowledge?: KnowledgeClient;
|
|
98
119
|
}
|
|
99
120
|
/** Per-agent override for the prompt composition and tool surface. */
|
|
100
121
|
export interface SandboxAgentPromptOptions {
|
|
@@ -42,19 +42,34 @@ import { createSubmitFileMetadataTool } from "../../tools/sandbox/submit-file-me
|
|
|
42
42
|
import { toSandboxPath } from "../../workspace/paths.js";
|
|
43
43
|
import { SANDBOX_AGENT_DEFAULT_MAX_ITERATIONS } from "./types.js";
|
|
44
44
|
import { createResolveCitationTool } from "../../tools/research/resolve-citation.js";
|
|
45
|
+
import { createKnowledgeTemplateTool } from "../../tools/knowledge/index.js";
|
|
45
46
|
/**
|
|
46
47
|
* Tools every sandbox agent receives — sandbox-environment introspection,
|
|
47
|
-
* library docs, and
|
|
48
|
-
* `meta.tools` so planner-facing metadata and the resolved tool record
|
|
48
|
+
* library docs, run inspection, and the knowledge template. Spread into each
|
|
49
|
+
* agent's `meta.tools` so planner-facing metadata and the resolved tool record
|
|
49
50
|
* stay in sync. Keep minimal — only tools every agent genuinely needs.
|
|
51
|
+
*
|
|
52
|
+
* The knowledge template is here, not on a specialist, because the template
|
|
53
|
+
* follows the step: the planner binds a template to a step from the
|
|
54
|
+
* recommendation and routes that step to whichever specialist owns its data
|
|
55
|
+
* object. The tool acts only on the template the step grounding names, thus
|
|
56
|
+
* a step without a template costs nothing, and it attaches only when the
|
|
57
|
+
* embedder binds a knowledge client (see the registry below).
|
|
50
58
|
*/
|
|
51
|
-
export const BASE_SANDBOX_TOOLS = [
|
|
59
|
+
export const BASE_SANDBOX_TOOLS = [
|
|
60
|
+
"listAvailablePackages",
|
|
61
|
+
"listAvailableRefs",
|
|
62
|
+
"resolveLibraryId",
|
|
63
|
+
"queryDocs",
|
|
64
|
+
"inspectRun",
|
|
65
|
+
"knowledgeTemplate",
|
|
66
|
+
];
|
|
52
67
|
/**
|
|
53
68
|
* Resolve `meta.tools` into the bio/research tools record. The workspace
|
|
54
69
|
* read/mutate tools and `inspect_data_profile` are added by `createSandboxAgent`
|
|
55
70
|
* regardless of meta — they are the always-on substrate, not in the allowlist.
|
|
56
71
|
*/
|
|
57
|
-
function resolveSandboxTools(deps, tools) {
|
|
72
|
+
function resolveSandboxTools(deps, tools, mutator) {
|
|
58
73
|
const ncbi = createNcbiTools(deps.bioKeys);
|
|
59
74
|
const chemDb = createChemDbTools(deps.bioKeys, { ...(deps.logger ? { logger: deps.logger } : {}) });
|
|
60
75
|
const amass = createAmassTools(deps.bioKeys);
|
|
@@ -97,6 +112,15 @@ function resolveSandboxTools(deps, tools) {
|
|
|
97
112
|
searchRegulatoryApprovals: amass.regulatoryApprovals,
|
|
98
113
|
targetSafety: targetSafetyTool,
|
|
99
114
|
comptox: chemDb.comptox,
|
|
115
|
+
knowledgeTemplate: deps.knowledge && mutator
|
|
116
|
+
? createKnowledgeTemplateTool({
|
|
117
|
+
client: deps.knowledge,
|
|
118
|
+
mutator,
|
|
119
|
+
...(deps.farmLockFile ? { farmLockFile: deps.farmLockFile } : {}),
|
|
120
|
+
...(deps.imagePackagesFile ? { imagePackagesFile: deps.imagePackagesFile } : {}),
|
|
121
|
+
...(deps.step.templateBinding ? { binding: deps.step.templateBinding } : {}),
|
|
122
|
+
})
|
|
123
|
+
: undefined,
|
|
100
124
|
};
|
|
101
125
|
const seen = new Set();
|
|
102
126
|
const resolved = [];
|
|
@@ -105,6 +129,12 @@ function resolveSandboxTools(deps, tools) {
|
|
|
105
129
|
continue;
|
|
106
130
|
seen.add(name);
|
|
107
131
|
const tool = registry[name];
|
|
132
|
+
// The knowledge template is the one optional member of the allowlist: every
|
|
133
|
+
// agent lists it through BASE_SANDBOX_TOOLS, and it attaches only when the
|
|
134
|
+
// embedder binds a client and the agent can write. Absence is a normal
|
|
135
|
+
// state, not a wiring fault.
|
|
136
|
+
if (name === "knowledgeTemplate" && !tool)
|
|
137
|
+
continue;
|
|
108
138
|
if (!tool) {
|
|
109
139
|
throw new Error(`createSandboxAgent: unknown SandboxToolName "${name}" — ` + `agent meta references a tool with no harness implementation.`);
|
|
110
140
|
}
|
|
@@ -124,24 +154,25 @@ function buildWorkspaceTools(deps, readOnly) {
|
|
|
124
154
|
// it is correct for non-step agents such as the data profiler too.
|
|
125
155
|
const hostWorkingDir = step.allowedWritePrefix;
|
|
126
156
|
const sandboxWorkingDir = toSandboxPath(step.workspaceRoot, step.analysisId, hostWorkingDir);
|
|
127
|
-
const
|
|
128
|
-
?
|
|
129
|
-
: (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
157
|
+
const mutator = readOnly
|
|
158
|
+
? undefined
|
|
159
|
+
: createWorkspaceMutator({
|
|
160
|
+
workspaceRoot: step.workspaceRoot,
|
|
161
|
+
analysisId: step.analysisId,
|
|
162
|
+
workingDir: hostWorkingDir,
|
|
163
|
+
...(lineageCollector ? { lineageCollector } : {}),
|
|
164
|
+
});
|
|
165
|
+
const mutateTools = mutator
|
|
166
|
+
? [createWriteFileTool({ mutator }), createEditFileTool({ mutator, workspaceFilesystem: workspaceFs, workingDir: hostWorkingDir })]
|
|
167
|
+
: [];
|
|
168
|
+
const tools = [
|
|
139
169
|
createExecuteCommandTool({
|
|
140
170
|
sandboxClient,
|
|
141
171
|
sandbox: step.sandbox,
|
|
142
172
|
deadlineMs: step.deadlineMs,
|
|
143
173
|
defaultCwd: sandboxWorkingDir,
|
|
144
174
|
...(lineageCollector ? { lineageCollector, mountRoot: `/${step.analysisId}` } : {}),
|
|
175
|
+
...(mutator ? { workspaceFilesystem: workspaceFs, mutator } : {}),
|
|
145
176
|
}),
|
|
146
177
|
...mutateTools,
|
|
147
178
|
createReadFileTool(workspaceFs, hostWorkingDir),
|
|
@@ -150,6 +181,7 @@ function buildWorkspaceTools(deps, readOnly) {
|
|
|
150
181
|
createGrepTool(workspaceFs, hostWorkingDir),
|
|
151
182
|
...(embedding ? [createWorkspaceSearchTool(pool, embedding)] : []),
|
|
152
183
|
];
|
|
184
|
+
return { tools, mutator };
|
|
153
185
|
}
|
|
154
186
|
/**
|
|
155
187
|
* Build one sandbox `AgentDefinition`. The system prompt is composed once
|
|
@@ -185,8 +217,9 @@ export function createSandboxAgent(deps, meta, body, opts = {}) {
|
|
|
185
217
|
const agentBody = [body, ...sandboxLayer].map((s) => s.trim()).join("\n\n");
|
|
186
218
|
const systemPrompt = composeSystemPrompt(agentBody);
|
|
187
219
|
const skillTools = deps.skillsDir ? Object.values(createSkillTools({ skillsDir: deps.skillsDir, skills: meta.skills })) : [];
|
|
220
|
+
const workspace = buildWorkspaceTools(deps, opts.readOnly ?? false);
|
|
188
221
|
const tools = [
|
|
189
|
-
...
|
|
222
|
+
...workspace.tools,
|
|
190
223
|
...(deps.toolOutputStore ? [createReadToolOutputTool(deps.toolOutputStore)] : []),
|
|
191
224
|
// Always-on, not in the `meta.tools` allowlist: the data profile is the only
|
|
192
225
|
// record of what the analysis's input dataset IS, and no file carries it (the
|
|
@@ -199,7 +232,7 @@ export function createSandboxAgent(deps, meta, body, opts = {}) {
|
|
|
199
232
|
// seam (the harness-sandbox-agents spec).
|
|
200
233
|
...(deps.extendAnalysisFarm ? [createLinkPackagesTool({ extendAnalysisFarm: deps.extendAnalysisFarm, analysisId: deps.step.analysisId })] : []),
|
|
201
234
|
...skillTools,
|
|
202
|
-
...resolveSandboxTools(deps, meta.tools),
|
|
235
|
+
...resolveSandboxTools(deps, meta.tools, workspace.mutator),
|
|
203
236
|
...(deps.blockerHolder ? [createReportBlockerTool(deps.blockerHolder)] : []),
|
|
204
237
|
...(deps.fileMetadata ? [createSubmitFileMetadataTool(deps.fileMetadata)] : []),
|
|
205
238
|
];
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { translationalSafetyAgentPrompt } from "../../prompts/sandbox/translational-safety-agent.js";
|
|
5
5
|
import { BASE_SANDBOX_TOOLS, createSandboxAgent } from "./shared.js";
|
|
6
|
+
/**
|
|
7
|
+
* Named once, because the capability phrase is the key of its own data requirement below and a
|
|
8
|
+
* plan step copies it verbatim. Two spellings of one capability would lose the pre-launch check.
|
|
9
|
+
*/
|
|
10
|
+
const MATCHED_NEGATIVE_CAPABILITY = "matched negative gene set for a tissue risk benchmark (annotation_free or tested_clean negatives) matched on tissue expression and PPI connectivity with mouse and human phenotype annotation exclusions";
|
|
6
11
|
export const meta = {
|
|
7
12
|
id: "translational-safety-agent",
|
|
8
13
|
capabilities: [
|
|
@@ -20,6 +25,8 @@ export const meta = {
|
|
|
20
25
|
"hematological toxicity assessment",
|
|
21
26
|
"mouse KO phenotype lookup",
|
|
22
27
|
"cross-species baseline expression lookup",
|
|
28
|
+
MATCHED_NEGATIVE_CAPABILITY,
|
|
29
|
+
"expression and PPI connectivity matching",
|
|
23
30
|
],
|
|
24
31
|
suitableFor: [
|
|
25
32
|
"safety-biomarkers",
|
|
@@ -29,6 +36,25 @@ export const meta = {
|
|
|
29
36
|
"clinical-safety-labs",
|
|
30
37
|
"drug-interaction-data",
|
|
31
38
|
"pharmacogenomic-data",
|
|
39
|
+
"gene-lists",
|
|
40
|
+
],
|
|
41
|
+
// The method of this capability reads six opt-in datasets. Without any one of them a negatives
|
|
42
|
+
// file would carry a label the run cannot defend, so the launch refuses rather than spend a run.
|
|
43
|
+
// The recommended datasets it also reads (the expression table, the network, the gene tables,
|
|
44
|
+
// the GO collection) stay out: a default store holds them, and declaring one would refuse a
|
|
45
|
+
// launch an embedder already provisioned.
|
|
46
|
+
dataRequirements: [
|
|
47
|
+
{
|
|
48
|
+
capability: MATCHED_NEGATIVE_CAPABILITY,
|
|
49
|
+
datasets: [
|
|
50
|
+
"string-aliases-human",
|
|
51
|
+
"mgi-mouse-human-homology",
|
|
52
|
+
"impc-phenotyping",
|
|
53
|
+
"hpo-annotations",
|
|
54
|
+
"opentargets-mouse-phenotype",
|
|
55
|
+
"opentargets-target-safety-event",
|
|
56
|
+
],
|
|
57
|
+
},
|
|
32
58
|
],
|
|
33
59
|
skills: ["translational-safety", "cheminformatics", "shared/omics-general"],
|
|
34
60
|
tools: [
|
|
@@ -12,7 +12,21 @@
|
|
|
12
12
|
* in the tool's `perSource` report; the rest of the call still answers.
|
|
13
13
|
*/
|
|
14
14
|
/** Closed allowlist of tools any sandbox agent may declare. */
|
|
15
|
-
export type SandboxToolName = "listAvailablePackages" | "listAvailableRefs" | "scanInputs" | "resolveLibraryId" | "queryDocs" | "inspectRun" | "pubmed" | "resolve_citation" | "searchGene" | "lookupAnnotation" | "searchInteractions" | "chembl" | "pubchem" | "opentargets" | "geneDiseaseEvidence" | "drugGeneInteractions" | "genePreclinicalProfile" | "searchFaers" | "searchClinicalTrials" | "searchGeoDatasets" | "searchRegulatoryApprovals" | "targetSafety" | "comptox";
|
|
15
|
+
export type SandboxToolName = "listAvailablePackages" | "listAvailableRefs" | "scanInputs" | "resolveLibraryId" | "queryDocs" | "inspectRun" | "pubmed" | "resolve_citation" | "searchGene" | "lookupAnnotation" | "searchInteractions" | "chembl" | "pubchem" | "opentargets" | "geneDiseaseEvidence" | "drugGeneInteractions" | "genePreclinicalProfile" | "searchFaers" | "searchClinicalTrials" | "searchGeoDatasets" | "searchRegulatoryApprovals" | "targetSafety" | "comptox" | "knowledgeTemplate";
|
|
16
|
+
/**
|
|
17
|
+
* The reference datasets that one capability of one agent cannot run without.
|
|
18
|
+
*
|
|
19
|
+
* A requirement belongs to a capability, never to the agent: an agent holds capabilities that need
|
|
20
|
+
* no reference data at all, so an agent-wide requirement would refuse a step that needs nothing.
|
|
21
|
+
* The skill of the agent still states what the data IS and how to use it. This states which catalog
|
|
22
|
+
* entry carries it, in a form a test can pin.
|
|
23
|
+
*/
|
|
24
|
+
export interface CapabilityDataRequirement {
|
|
25
|
+
/** One phrase of the same agent's `capabilities`, verbatim — the step copies it from the catalog. */
|
|
26
|
+
readonly capability: string;
|
|
27
|
+
/** Reference-catalog dataset ids. Declare only an opt-in dataset: a recommended one is already in a default store. */
|
|
28
|
+
readonly datasets: readonly string[];
|
|
29
|
+
}
|
|
16
30
|
/** Planner-facing metadata + tool allowlist for one sandbox agent. */
|
|
17
31
|
export interface AgentMeta {
|
|
18
32
|
/** Stable agent id — also the key in the catalog and the `AgentDefinition.id`. */
|
|
@@ -29,6 +43,15 @@ export interface AgentMeta {
|
|
|
29
43
|
readonly defaultMaxSteps?: number;
|
|
30
44
|
/** False for agents the planner must NOT assign to plan steps (executors, the profiler). */
|
|
31
45
|
readonly plannable?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Per-capability reference-data requirements. Omitted by an agent whose capabilities all run on
|
|
48
|
+
* what a default store holds.
|
|
49
|
+
*
|
|
50
|
+
* The pre-launch pass resolves these against the live store, so an absent dataset refuses the
|
|
51
|
+
* launch instead of costing a run. The planner-facing projection deliberately drops this field:
|
|
52
|
+
* a prompt names no dataset, so the ids reach a caller through a tool and the launch through here.
|
|
53
|
+
*/
|
|
54
|
+
readonly dataRequirements?: readonly CapabilityDataRequirement[];
|
|
32
55
|
}
|
|
33
56
|
/** Default runaway-guard for sandbox agents. */
|
|
34
57
|
export declare const SANDBOX_AGENT_DEFAULT_MAX_ITERATIONS = 200;
|
|
@@ -15,6 +15,20 @@ export interface PlannerAgentMeta {
|
|
|
15
15
|
}
|
|
16
16
|
/** The sandbox agents the planner may assign to analysis-plan steps. */
|
|
17
17
|
export declare const PLANNABLE_AGENT_CATALOG: readonly PlannerAgentMeta[];
|
|
18
|
+
/**
|
|
19
|
+
* The reference datasets one capability of one agent cannot run without, by catalog id.
|
|
20
|
+
*
|
|
21
|
+
* Empty for an unknown agent, for an agent that declares nothing, and for a phrase no declaration
|
|
22
|
+
* matches. An unmatched phrase must NOT refuse a launch: the pass can only demand what a
|
|
23
|
+
* declaration states, so a paraphrased phrase degrades to the behaviour of a step with no phrase.
|
|
24
|
+
*/
|
|
25
|
+
export declare function capabilityDataRequirement(agentId: string, capability: string): readonly string[];
|
|
26
|
+
/** Every declared requirement, for a caller that asks what a capability wants before it plans. */
|
|
27
|
+
export declare function declaredDataRequirements(): readonly {
|
|
28
|
+
agentId: string;
|
|
29
|
+
capability: string;
|
|
30
|
+
datasets: readonly string[];
|
|
31
|
+
}[];
|
|
18
32
|
/**
|
|
19
33
|
* Ids of the agents the planner may assign — the `z.enum` domain for
|
|
20
34
|
* `PlanStepSchema.agent`. A non-empty tuple, as `z.enum` requires.
|
|
@@ -16,6 +16,25 @@ export const PLANNABLE_AGENT_CATALOG = Object.values(SANDBOX_AGENT_META)
|
|
|
16
16
|
capabilities: [...meta.capabilities],
|
|
17
17
|
suitableFor: [...meta.suitableFor],
|
|
18
18
|
}));
|
|
19
|
+
/**
|
|
20
|
+
* The reference datasets one capability of one agent cannot run without, by catalog id.
|
|
21
|
+
*
|
|
22
|
+
* Empty for an unknown agent, for an agent that declares nothing, and for a phrase no declaration
|
|
23
|
+
* matches. An unmatched phrase must NOT refuse a launch: the pass can only demand what a
|
|
24
|
+
* declaration states, so a paraphrased phrase degrades to the behaviour of a step with no phrase.
|
|
25
|
+
*/
|
|
26
|
+
export function capabilityDataRequirement(agentId, capability) {
|
|
27
|
+
const entry = SANDBOX_AGENT_META[agentId]?.dataRequirements?.find((requirement) => requirement.capability === capability);
|
|
28
|
+
return entry?.datasets ?? [];
|
|
29
|
+
}
|
|
30
|
+
/** Every declared requirement, for a caller that asks what a capability wants before it plans. */
|
|
31
|
+
export function declaredDataRequirements() {
|
|
32
|
+
return Object.values(SANDBOX_AGENT_META).flatMap((meta) => (meta.dataRequirements ?? []).map((requirement) => ({
|
|
33
|
+
agentId: meta.id,
|
|
34
|
+
capability: requirement.capability,
|
|
35
|
+
datasets: requirement.datasets,
|
|
36
|
+
})));
|
|
37
|
+
}
|
|
19
38
|
/**
|
|
20
39
|
* Ids of the agents the planner may assign — the `z.enum` domain for
|
|
21
40
|
* `PlanStepSchema.agent`. A non-empty tuple, as `z.enum` requires.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approve a stored plan by its id, with no model call.
|
|
3
|
+
*
|
|
4
|
+
* A host calls this when a person approves a plan. The function refuses a thread or a plan that does
|
|
5
|
+
* not belong to the analysis, and a plan that a newer plan replaced. Then it starts the run of the plan,
|
|
6
|
+
* emits the `approve-plan` provenance event, and appends one record to the thread. The record tells the
|
|
7
|
+
* model that the run started, and it shows the person the approval and the run card.
|
|
8
|
+
*
|
|
9
|
+
* The function is an API boundary: it gives a `Result` for each outcome and never rejects for one. The
|
|
10
|
+
* host gives the identity and the auth of the session, because the harness forwards the auth and never
|
|
11
|
+
* mints it. The harness gives the provenance of the session.
|
|
12
|
+
*/
|
|
13
|
+
import { ResultAsync } from "neverthrow";
|
|
14
|
+
import type { AuthContext, Identity } from "../auth/types.js";
|
|
15
|
+
import type { ChatMessage } from "../contracts/message.js";
|
|
16
|
+
import { type ProvenanceSeam } from "../provenance/seam.js";
|
|
17
|
+
import { type LaunchPlanRunDeps, type LaunchPlanRunRefusal } from "../tools/execute-analysis.js";
|
|
18
|
+
/** The deps of an approval: the deps of the launch, and the provenance sink of the host. */
|
|
19
|
+
export type ApprovePlanDeps = LaunchPlanRunDeps & {
|
|
20
|
+
readonly provenance?: ProvenanceSeam;
|
|
21
|
+
};
|
|
22
|
+
export type ApprovePlanInput = {
|
|
23
|
+
analysisId: string;
|
|
24
|
+
threadId: string;
|
|
25
|
+
planId: string;
|
|
26
|
+
/** The newest run of the plan in the view that the person approved, or `null` when that view showed no run. */
|
|
27
|
+
newestRunId: string | null;
|
|
28
|
+
identity: Identity;
|
|
29
|
+
auth: AuthContext;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The outcome of an approval. `started` is false when a run of the plan was already active, or when the
|
|
33
|
+
* view of the person was stale: the approval then changed nothing, `runId` names the active run or the
|
|
34
|
+
* newest run of the plan, and `message` is `null`. `message` is also `null` when the record
|
|
35
|
+
* could not be appended, because the run started anyway. It has no `createdAt`: the transcript read
|
|
36
|
+
* adds the time of the row.
|
|
37
|
+
*/
|
|
38
|
+
export type PlanApproval = {
|
|
39
|
+
planId: string;
|
|
40
|
+
runId: string;
|
|
41
|
+
started: boolean;
|
|
42
|
+
message: ChatMessage | null;
|
|
43
|
+
};
|
|
44
|
+
export type ApprovePlanRefusal = LaunchPlanRunRefusal | {
|
|
45
|
+
type: "thread_not_found";
|
|
46
|
+
} | {
|
|
47
|
+
type: "superseded";
|
|
48
|
+
supersededBy: string;
|
|
49
|
+
};
|
|
50
|
+
/** Approve the plan `planId` on the thread `threadId` of the analysis, and start its run. */
|
|
51
|
+
export declare function approvePlan(deps: ApprovePlanDeps, input: ApprovePlanInput): ResultAsync<PlanApproval, ApprovePlanRefusal>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approve a stored plan by its id, with no model call.
|
|
3
|
+
*
|
|
4
|
+
* A host calls this when a person approves a plan. The function refuses a thread or a plan that does
|
|
5
|
+
* not belong to the analysis, and a plan that a newer plan replaced. Then it starts the run of the plan,
|
|
6
|
+
* emits the `approve-plan` provenance event, and appends one record to the thread. The record tells the
|
|
7
|
+
* model that the run started, and it shows the person the approval and the run card.
|
|
8
|
+
*
|
|
9
|
+
* The function is an API boundary: it gives a `Result` for each outcome and never rejects for one. The
|
|
10
|
+
* host gives the identity and the auth of the session, because the harness forwards the auth and never
|
|
11
|
+
* mints it. The harness gives the provenance of the session.
|
|
12
|
+
*/
|
|
13
|
+
import { ResultAsync, err, ok } from "neverthrow";
|
|
14
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
15
|
+
import { buildRunCardData } from "../memory/card-builders.js";
|
|
16
|
+
import { conversationUIToChatMessages } from "../memory/conversation-display-storage.js";
|
|
17
|
+
import { conversationRecordTurn, createThreadHistory } from "../memory/thread-history.js";
|
|
18
|
+
import { createThreadStore } from "../memory/thread-store.js";
|
|
19
|
+
import { bindSessionEmit } from "../provenance/seam.js";
|
|
20
|
+
import { loadPlanRecord } from "../state/index.js";
|
|
21
|
+
import { launchPlanRun } from "../tools/execute-analysis.js";
|
|
22
|
+
const APPROVAL_PROVENANCE = { agentId: "plan-approval", callPath: ["plan-approval"] };
|
|
23
|
+
function storeFailed(cause) {
|
|
24
|
+
return err({ type: "store_failed", cause });
|
|
25
|
+
}
|
|
26
|
+
/** The refusals that need no launch: a thread or a plan that does not belong, and a replaced plan. */
|
|
27
|
+
async function checkApprovable(deps, input) {
|
|
28
|
+
const read = await ResultAsync.combine([
|
|
29
|
+
createThreadStore(deps.pool).getThread(input.threadId),
|
|
30
|
+
loadPlanRecord(deps.pool, input.planId, { analysisId: input.analysisId }),
|
|
31
|
+
]);
|
|
32
|
+
if (read.isErr())
|
|
33
|
+
return storeFailed(read.error);
|
|
34
|
+
const [thread, record] = read.value;
|
|
35
|
+
if (thread === null || thread.analysisId !== input.analysisId)
|
|
36
|
+
return err({ type: "thread_not_found" });
|
|
37
|
+
if (record === null)
|
|
38
|
+
return err({ type: "plan_not_found", planId: input.planId });
|
|
39
|
+
if (record.supersededBy !== null)
|
|
40
|
+
return err({ type: "superseded", supersededBy: record.supersededBy });
|
|
41
|
+
return ok(undefined);
|
|
42
|
+
}
|
|
43
|
+
async function approve(deps, input) {
|
|
44
|
+
const logger = (deps.logger ?? createNoopLogger()).named("approve-plan");
|
|
45
|
+
const { analysisId, threadId, planId } = input;
|
|
46
|
+
const approvable = await checkApprovable(deps, input);
|
|
47
|
+
if (approvable.isErr())
|
|
48
|
+
return err(approvable.error);
|
|
49
|
+
// Read before the launch: the kernel writes the approval at this time, and the run writes
|
|
50
|
+
// `run_started` from its workflow body after the launch, so the approval precedes the start.
|
|
51
|
+
const approvedAtMs = Date.now();
|
|
52
|
+
const session = {
|
|
53
|
+
identity: input.identity,
|
|
54
|
+
auth: input.auth,
|
|
55
|
+
scope: { kind: "analysis", analysisId, threadId },
|
|
56
|
+
provenance: APPROVAL_PROVENANCE,
|
|
57
|
+
};
|
|
58
|
+
const launched = await launchPlanRun(deps, { session, planId, newestRunId: input.newestRunId });
|
|
59
|
+
if (launched.isErr())
|
|
60
|
+
return err(launched.error);
|
|
61
|
+
const { runId, started } = launched.value;
|
|
62
|
+
if (!started)
|
|
63
|
+
return ok({ planId, runId, started: false, message: null });
|
|
64
|
+
bindSessionEmit(deps.provenance, logger)({ type: "approve-plan", analysisId, threadId, planId, runId, atMs: approvedAtMs });
|
|
65
|
+
const card = await buildRunCardData(deps.pool, { planId, analysisId, runId }).match((data) => data, (cause) => {
|
|
66
|
+
logger.error("the run card of an approval could not be read", { analysisId, threadId, planId, runId, ...logger.errorFields(cause) });
|
|
67
|
+
return null;
|
|
68
|
+
});
|
|
69
|
+
const approval = { type: "data-plan-approval", planId, runId, approvedAt: new Date(approvedAtMs).toISOString() };
|
|
70
|
+
const turn = conversationRecordTurn(`The person approved plan ${planId}. Run ${runId} started.`, [
|
|
71
|
+
approval,
|
|
72
|
+
...(card === null ? [] : [{ type: "data-run-card", ...card }]),
|
|
73
|
+
]);
|
|
74
|
+
const appended = await createThreadHistory(deps.pool, logger).appendTurn(threadId, turn);
|
|
75
|
+
if (appended.isErr()) {
|
|
76
|
+
logger.error("the approval record could not be appended", { analysisId, threadId, planId, runId, ...logger.errorFields(appended.error) });
|
|
77
|
+
return ok({ planId, runId, started: true, message: null });
|
|
78
|
+
}
|
|
79
|
+
return ok({ planId, runId, started: true, message: conversationUIToChatMessages(turn.displayMessages)[0] ?? null });
|
|
80
|
+
}
|
|
81
|
+
/** Approve the plan `planId` on the thread `threadId` of the analysis, and start its run. */
|
|
82
|
+
export function approvePlan(deps, input) {
|
|
83
|
+
return new ResultAsync(approve(deps, input));
|
|
84
|
+
}
|
package/dist/app/chat-turn.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* values, and the harness stores the opening, each round, and the outcome.
|
|
5
5
|
*/
|
|
6
6
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
7
|
+
import { rulesForContextWindow } from "../loop/compaction.js";
|
|
7
8
|
import { finalText, runAgent } from "../loop/run-agent.js";
|
|
8
9
|
import { passthroughStep } from "../loop/run-step.js";
|
|
9
10
|
import { compactionExchangeOf, syntheticUserMessage } from "../memory/ai-sdk-message-storage.js";
|
|
@@ -244,10 +245,12 @@ function compactionPolicy(deps, analysisId, params, threadType, agent, emit) {
|
|
|
244
245
|
// A report thread reads a frozen copy of the working memory, thus its agent declares no memory tool.
|
|
245
246
|
const remembers = agent.tools.some((tool) => tool.id === WORKING_MEMORY_TOOL_ID);
|
|
246
247
|
const workingMemory = createWorkingMemory(deps.pool);
|
|
248
|
+
// A streaming provider sends each text delta with no source, thus a delta of the summary would show as a reply.
|
|
249
|
+
const provider = params.chat((event) => (event.type === "text-delta" ? undefined : emit(event)));
|
|
247
250
|
return {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
provider
|
|
251
|
+
// The capabilities are read for each turn, because a host can switch the model of the conversation between turns.
|
|
252
|
+
...rulesForContextWindow(agent.compaction, provider.capabilities.contextWindow),
|
|
253
|
+
provider,
|
|
251
254
|
request: remembers ? MEMORY_COMPACTION_REQUEST : SUMMARY_COMPACTION_REQUEST,
|
|
252
255
|
mask: remembers ? { allow: [WORKING_MEMORY_TOOL_ID] } : "none",
|
|
253
256
|
keepFirstTurn: threadType === "report",
|
|
@@ -65,6 +65,6 @@ export declare function profileFileRecords(result: DataProfileResult): DataProfi
|
|
|
65
65
|
* rows fall back to coverage, then to summed kind counts, then to `null`: reporting the
|
|
66
66
|
* described-file count would be a dataset size the row cannot support.
|
|
67
67
|
*/
|
|
68
|
-
export declare function profileDatasetFileCount(result: DataProfileResult): number | null;
|
|
68
|
+
export declare function profileDatasetFileCount(result: Pick<DataProfileResult, "partition" | "coverage" | "kinds">): number | null;
|
|
69
69
|
/** Dataset-wide findings the agent wrote, from whichever field the snapshot carries them in. */
|
|
70
70
|
export declare function profileCaveats(result: DataProfileResult): string[];
|
package/dist/app/run-activity.js
CHANGED
|
@@ -4,21 +4,19 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/** Maximum detailed run rows paid into every conversation turn. */
|
|
6
6
|
export const RUN_ACTIVITY_DETAIL_LIMIT = 20;
|
|
7
|
-
function renderRows(
|
|
7
|
+
function renderRows(rows) {
|
|
8
8
|
if (rows.length === 0)
|
|
9
9
|
return [];
|
|
10
|
-
return [
|
|
10
|
+
return ["Running:", ...rows.map((run) => `- runId: ${run.runId} | planId: ${run.planId ?? "none"} | startedAt: ${run.startedAt}`)];
|
|
11
11
|
}
|
|
12
12
|
/** Render the bounded run-activity snapshot of a conversation turn. */
|
|
13
13
|
export function renderRunActivity(activity) {
|
|
14
14
|
const lines = ["[Run Activity]"];
|
|
15
15
|
if (activity.total === 0) {
|
|
16
|
-
lines.push("No runs are currently running
|
|
16
|
+
lines.push("No runs are currently running.");
|
|
17
17
|
return lines.join("\n");
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
const suspended = activity.runs.filter((run) => run.status === "suspended_insufficient_funds");
|
|
21
|
-
lines.push(...renderRows("Running", running), ...renderRows("Suspended", suspended));
|
|
19
|
+
lines.push(...renderRows(activity.runs.filter((run) => run.status === "running")));
|
|
22
20
|
const omitted = activity.total - activity.runs.length;
|
|
23
21
|
if (omitted > 0) {
|
|
24
22
|
lines.push(`Showing ${activity.runs.length} of ${activity.total} non-terminal runs; ${omitted} omitted. Use inspect_run to page the full list.`);
|
|
@@ -49,8 +49,10 @@ export interface EnvironmentStorePaths {
|
|
|
49
49
|
readonly farmLockFile?: string | ((analysisId: string) => string);
|
|
50
50
|
/**
|
|
51
51
|
* Host path of the image inventory record, `image-packages.json` at the
|
|
52
|
-
* root of the package store — the image-owned tools
|
|
53
|
-
*
|
|
52
|
+
* root of the package store — the image-owned tools, the Node packages,
|
|
53
|
+
* and the R packages that ship with the R runtime, which
|
|
54
|
+
* `list_available_packages` merges into its report and which the
|
|
55
|
+
* knowledge tools read for the environment of a step. Omit when the
|
|
54
56
|
* host's own process sees the store at the container mountpoint, which
|
|
55
57
|
* makes `/mnt/libs/image-packages.json` correct as-is; set it when the
|
|
56
58
|
* host reads the store somewhere else. An absent or invalid record
|