@botbotgo/agent-harness 0.0.43 → 0.0.44

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/README.md CHANGED
@@ -15,7 +15,7 @@ What it provides:
15
15
 
16
16
  - a small runtime API centered on `createAgentHarness(...)`, `run(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
17
17
  - YAML-defined runtime assembly for hosts, models, routing, recovery, concurrency, MCP, and maintenance policy
18
- - backend-adapted execution with current DeepAgents-first defaults and LangChain v1 compatibility
18
+ - backend-adapted execution with a generic runtime contract and current LangChain v1 / DeepAgents adapters
19
19
  - local `resources/tools/` and `resources/skills/` loading
20
20
  - persisted runs, threads, approvals, events, and resumable checkpoints
21
21
 
@@ -35,6 +35,9 @@ your-workspace/
35
35
  agent-context.md
36
36
  workspace.yaml
37
37
  models.yaml
38
+ stores.yaml
39
+ tools.yaml
40
+ mcp.yaml
38
41
  agents/
39
42
  direct.yaml
40
43
  orchestra.yaml
@@ -204,6 +207,9 @@ Core workspace files:
204
207
  - `config/workspace.yaml`
205
208
  - `config/agent-context.md`
206
209
  - `config/models.yaml`
210
+ - `config/stores.yaml`
211
+ - `config/tools.yaml`
212
+ - `config/mcp.yaml`
207
213
  - `config/agents/direct.yaml`
208
214
  - `config/agents/orchestra.yaml`
209
215
  - `resources/package.json`
@@ -15,17 +15,17 @@ spec:
15
15
  # =====================
16
16
  # Runtime Agent Features
17
17
  # =====================
18
- # Shared LangChain v1 / DeepAgents feature: model ref for the underlying LLM used by the
19
- # direct-response agent. This should point at a cheap, fast, general-purpose chat model,
20
- # because `direct` is intended to be the low-latency path for simple requests.
18
+ # Upstream execution feature: model ref for the underlying LLM used by the direct-response agent.
19
+ # This should point at a cheap, fast, general-purpose chat model, because `direct` is intended
20
+ # to be the low-latency path for simple requests.
21
21
  modelRef: model/default
22
- # Shared LangChain v1 / DeepAgents feature: checkpointer config passed into the runtime.
22
+ # Runtime execution feature: checkpointer config passed into the selected backend adapter.
23
23
  # Even the lightweight direct path can benefit from resumable state during interactive use.
24
24
  # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
25
25
  # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
26
26
  checkpointer:
27
- kind: MemorySaver
28
- # LangChainAgent aligned feature: system prompt for the lightweight direct-response host.
27
+ ref: checkpointer/default
28
+ # Upstream execution feature: system prompt for the lightweight direct-response host.
29
29
  # This prompt should keep the agent focused on:
30
30
  # - answering simple requests in one turn
31
31
  # - staying lightweight instead of planning or orchestrating
@@ -4,7 +4,7 @@ apiVersion: agent-harness/v1alpha1
4
4
  # Prefer the generic `Agent` form and select the concrete execution backend under `spec.execution`.
5
5
  kind: Agent
6
6
  metadata:
7
- # agent-harness feature: stable object id used for refs and default DeepAgents name inference.
7
+ # agent-harness feature: stable object id used for refs and runtime naming.
8
8
  name: orchestra
9
9
  # agent-harness feature: human-readable summary for inventory and UI.
10
10
  description: Default execution host. Answer directly when possible, use local tools and skills first, and delegate only when a specialist is a better fit. Not a reflex delegation-only planner.
@@ -15,17 +15,17 @@ spec:
15
15
  # =====================
16
16
  # Runtime Agent Features
17
17
  # =====================
18
- # DeepAgents aligned feature: model ref for the underlying LLM used by `createDeepAgent(...)`.
18
+ # Upstream execution feature: model ref for the underlying LLM used by this execution host.
19
19
  modelRef: model/default
20
- # Shared LangChain v1 / DeepAgents feature: checkpointer config passed into the upstream runtime.
20
+ # Runtime execution feature: checkpointer config passed into the selected backend adapter.
21
21
  # This persists resumable graph state for this agent.
22
22
  # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
23
23
  # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
24
24
  checkpointer:
25
- # kind: FileCheckpointer
26
- kind: MemorySaver
25
+ # ref: checkpointer/sqlite
26
+ ref: checkpointer/default
27
27
  memory:
28
- # DeepAgents aligned feature: bootstrap memory sources supplied to the deep agent at construction time.
28
+ # Upstream execution feature: bootstrap memory sources supplied to the selected backend at construction time.
29
29
  # These paths resolve relative to the workspace root unless they are already absolute.
30
30
  # Treat this as agent-owned startup context, not as a dynamic long-term memory sink:
31
31
  # - keep `systemPrompt` for stable role, boundaries, and hard behavioral rules
@@ -35,13 +35,12 @@ spec:
35
35
  # Updating these files changes future agent constructions, but they are still bootstrap inputs rather than
36
36
  # self-updating runtime memory.
37
37
  - path: config/agent-context.md
38
- # DeepAgents aligned feature: store config passed into `createDeepAgent({ store })`.
39
- # This is the LangGraph store used by `StoreBackend` routes inside the DeepAgents backend.
38
+ # Upstream execution feature: store config passed into the selected backend adapter.
39
+ # In the default deepagent adapter this is the LangGraph store used by `StoreBackend` routes.
40
40
  # Available `kind` options in this harness: `FileStore`, `InMemoryStore`, `RedisStore`, `PostgresStore`.
41
41
  store:
42
- kind: FileStore
43
- path: store.json
44
- # DeepAgents aligned feature: backend config passed into `createDeepAgent({ backend })`.
42
+ ref: store/default
43
+ # Upstream execution feature: backend config passed into the selected backend adapter.
45
44
  # This directly defines the backend topology for this agent:
46
45
  # - workspace execution uses a lightweight VFS sandbox
47
46
  # - long-term memory under `/memories/*` uses `StoreBackend`
@@ -59,8 +58,8 @@ spec:
59
58
  /memories/:
60
59
  # Available route backend `kind` options today: `StoreBackend`.
61
60
  kind: StoreBackend
62
- # DeepAgents aligned feature: system prompt for the orchestration deep agent.
63
- # This becomes the top-level instruction block for the upstream DeepAgents runtime and should hold the
61
+ # Upstream execution feature: system prompt for the orchestration host.
62
+ # This becomes the top-level instruction block for the selected execution backend and should hold the
64
63
  # agent's durable role, priorities, and behavioral guardrails rather than bulky project facts.
65
64
  systemPrompt: |-
66
65
  You are the orchestra agent.
@@ -0,0 +1,20 @@
1
+ # agent-harness feature: schema version for reusable MCP server objects.
2
+ apiVersion: agent-harness/v1alpha1
3
+ # This first-layer catalog is the default place to register reusable McpServer objects and MCP-backed Tool objects.
4
+ # Examples:
5
+ # items:
6
+ # - kind: McpServer
7
+ # metadata:
8
+ # name: browser
9
+ # spec:
10
+ # command: node
11
+ # args: ["./mcp-browser-server.mjs"]
12
+ #
13
+ # - kind: Tool
14
+ # metadata:
15
+ # name: browser_navigate
16
+ # spec:
17
+ # mcp:
18
+ # serverRef: mcp/browser
19
+ # tool: navigate
20
+ items: []
@@ -0,0 +1,19 @@
1
+ # agent-harness feature: schema version for reusable persistence presets.
2
+ apiVersion: agent-harness/v1alpha1
3
+ items:
4
+ # agent-harness feature: reusable store preset for agent backends that need a durable key-value store.
5
+ - kind: Store
6
+ metadata:
7
+ name: default
8
+ description: Default file-backed store preset for runtime-managed agent state.
9
+ spec:
10
+ storeKind: FileStore
11
+ path: store.json
12
+
13
+ # agent-harness feature: reusable checkpointer preset for resumable execution state.
14
+ - kind: Checkpointer
15
+ metadata:
16
+ name: default
17
+ description: Default in-memory checkpointer preset for lightweight local development.
18
+ spec:
19
+ checkpointerKind: MemorySaver
@@ -0,0 +1,13 @@
1
+ # agent-harness feature: schema version for reusable tool objects.
2
+ apiVersion: agent-harness/v1alpha1
3
+ # This first-layer catalog is the default place to register reusable Tool objects that agents can reference.
4
+ # Examples:
5
+ # items:
6
+ # - kind: Tool
7
+ # metadata:
8
+ # name: repo_search
9
+ # description: Workspace-local repo search tool.
10
+ # spec:
11
+ # backend:
12
+ # operation: repo_search
13
+ items: []
@@ -23,21 +23,22 @@ spec:
23
23
  runRoot: ./.agent
24
24
 
25
25
  # agent-harness feature: optional host-router prompt override used when the runtime chooses between
26
- # top-level host agents such as a main DeepAgent host and an optional low-latency side host.
26
+ # top-level host agents such as a main execution host and an optional low-latency side host.
27
27
  # Use placeholders so the same prompt can survive host renames:
28
28
  # - {{primaryAgentId}}
29
29
  # - {{primaryDescription}}
30
30
  # - {{secondaryAgentId}}
31
31
  # - {{secondaryDescription}}
32
32
  routing:
33
- # agent-harness feature: DeepAgents-first default host selected when no explicit routing rule matches.
34
- # Best practice is to point this at the main DeepAgent entry host and treat lighter agents as explicit opt-in.
33
+ # agent-harness feature: default host selected when no explicit routing rule matches.
34
+ # Best practice is to point this at the main execution host and treat lighter agents as explicit opt-in.
35
35
  defaultAgentId: orchestra
36
36
  # agent-harness feature: optional model-driven host classification fallback.
37
37
  # Keep this disabled unless you specifically need host-level model selection.
38
38
  modelRouting: false
39
39
  # agent-harness feature: ordered host-routing rules evaluated before model-driven classification.
40
- # These rules only choose which host profile starts the run; DeepAgents still owns planning, tools, and long-running execution.
40
+ # These rules only choose which host profile starts the run; the selected backend still owns
41
+ # planning, tools, and long-running execution behavior after routing picks a host.
41
42
  # The first matching rule wins. Use this when you want stable routing behavior in YAML instead of code.
42
43
  #
43
44
  # Supported match fields on each rule:
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { defaultResourceSkillsRoot } from "./resource/resource.js";
3
3
  import { isExternalSourceLocator, resolveExternalResourcePath } from "./resource/sources.js";
4
+ import { hasAgentSystemPrompt } from "./workspace/support/agent-capabilities.js";
4
5
  const toolKindAdapters = new Map();
5
6
  const skillSourceResolvers = new Map();
6
7
  const skillInheritancePolicies = new Map();
@@ -231,9 +232,7 @@ registerSkillSourceResolver({
231
232
  registerSkillInheritancePolicy({
232
233
  kind: "default",
233
234
  apply({ agent, ownSkills, parentSkills }) {
234
- const systemPrompt = agent.deepAgentConfig?.systemPrompt;
235
- const hasSystemPrompt = typeof systemPrompt === "string" && systemPrompt.trim().length > 0;
236
- if (hasSystemPrompt && agent.id.includes(".")) {
235
+ if (hasAgentSystemPrompt(agent) && agent.id.includes(".")) {
237
236
  return ownSkills;
238
237
  }
239
238
  return Array.from(new Set([...parentSkills, ...ownSkills]));
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.42";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.43";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.42";
1
+ export const AGENT_HARNESS_VERSION = "0.0.43";
@@ -1,4 +1,18 @@
1
1
  export declare function escapeHtml(value: string): string;
2
+ /** CSS class for anchors that should open in the host app embedded browser (Wallee). */
3
+ export declare const WALLEE_OUTPUT_LINK_CLASS = "wallee-output-link";
4
+ /** `data-wallee-url` — target URL for the embedded browser (http/https only). */
5
+ export declare const WALLEE_BROWSER_URL_ATTR = "data-wallee-url";
6
+ export declare function isAllowedWalleeBrowserUrl(url: string): boolean;
7
+ /**
8
+ * Escape plain text and wrap http(s) URLs in Wallee output anchors (open in host embedded browser).
9
+ */
10
+ export declare function linkifyPlainTextForWalleeBrowser(text: string): string;
11
+ /**
12
+ * Like {@link markdownToHtml} but inline http(s) URLs and markdown links `[label](https://…)` render as
13
+ * Wallee embedded-browser anchors (`wallee-output-link` + `data-wallee-url`).
14
+ */
15
+ export declare function markdownToWalleeOutputHtml(markdown: string): string;
2
16
  export declare function markdownToHtml(markdown: string): string;
3
17
  export declare function markdownToConsole(markdown: string): string;
4
18
  export declare function renderTemplate(data: Record<string, unknown>, template: string): string;
@@ -8,6 +8,152 @@ export function escapeHtml(value) {
8
8
  .replaceAll('"', "&quot;")
9
9
  .replaceAll("'", "&#39;");
10
10
  }
11
+ /** CSS class for anchors that should open in the host app embedded browser (Wallee). */
12
+ export const WALLEE_OUTPUT_LINK_CLASS = "wallee-output-link";
13
+ /** `data-wallee-url` — target URL for the embedded browser (http/https only). */
14
+ export const WALLEE_BROWSER_URL_ATTR = "data-wallee-url";
15
+ export function isAllowedWalleeBrowserUrl(url) {
16
+ try {
17
+ const u = new URL(url);
18
+ return u.protocol === "http:" || u.protocol === "https:";
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ function walleeOutputAnchor(url, labelEscaped) {
25
+ return `<a class="${WALLEE_OUTPUT_LINK_CLASS}" ${WALLEE_BROWSER_URL_ATTR}="${escapeHtml(url)}" href="#">${labelEscaped}</a>`;
26
+ }
27
+ /**
28
+ * Escape plain text and wrap http(s) URLs in Wallee output anchors (open in host embedded browser).
29
+ */
30
+ export function linkifyPlainTextForWalleeBrowser(text) {
31
+ const urlRe = /\bhttps?:\/\/[^\s<>"']+/g;
32
+ const parts = [];
33
+ let last = 0;
34
+ let m;
35
+ while ((m = urlRe.exec(text)) !== null) {
36
+ parts.push(escapeHtml(text.slice(last, m.index)));
37
+ const raw = m[0];
38
+ const trimmed = raw.replace(/[.,;:!?)\]]+$/u, "");
39
+ const rest = raw.slice(trimmed.length);
40
+ if (isAllowedWalleeBrowserUrl(trimmed)) {
41
+ parts.push(walleeOutputAnchor(trimmed, escapeHtml(trimmed)));
42
+ if (rest) {
43
+ parts.push(escapeHtml(rest));
44
+ }
45
+ }
46
+ else {
47
+ parts.push(escapeHtml(raw));
48
+ }
49
+ last = m.index + raw.length;
50
+ }
51
+ parts.push(escapeHtml(text.slice(last)));
52
+ return parts.join("");
53
+ }
54
+ function applyBasicInlineMarkdown(escaped) {
55
+ return escaped
56
+ .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
57
+ .replace(/\*(.+?)\*/g, "<em>$1</em>")
58
+ .replace(/`([^`]+)`/g, "<code>$1</code>");
59
+ }
60
+ function linkifyPlainTextSegmentWithWalleeMarkdown(text) {
61
+ const mdLinkPattern = /\[([^\]]*)\]\((https?:\/\/[^)\s]+)\)/g;
62
+ const segments = [];
63
+ let lastIndex = 0;
64
+ let m;
65
+ while ((m = mdLinkPattern.exec(text)) !== null) {
66
+ if (m.index > lastIndex) {
67
+ segments.push({ type: "text", text: text.slice(lastIndex, m.index) });
68
+ }
69
+ segments.push({ type: "mdlink", label: m[1], url: m[2] });
70
+ lastIndex = m.index + m[0].length;
71
+ }
72
+ if (lastIndex < text.length) {
73
+ segments.push({ type: "text", text: text.slice(lastIndex) });
74
+ }
75
+ if (segments.length === 0) {
76
+ segments.push({ type: "text", text });
77
+ }
78
+ return segments
79
+ .map((seg) => {
80
+ if (seg.type === "mdlink") {
81
+ if (!isAllowedWalleeBrowserUrl(seg.url)) {
82
+ return `${escapeHtml(seg.label)} (${escapeHtml(seg.url)})`;
83
+ }
84
+ return walleeOutputAnchor(seg.url, escapeHtml(seg.label));
85
+ }
86
+ return linkifyBareUrlsWithInlineMarkdown(seg.text);
87
+ })
88
+ .join("");
89
+ }
90
+ function linkifyBareUrlsWithInlineMarkdown(text) {
91
+ const urlRe = /\bhttps?:\/\/[^\s<>"']+/g;
92
+ const parts = [];
93
+ let last = 0;
94
+ let m;
95
+ while ((m = urlRe.exec(text)) !== null) {
96
+ parts.push(applyBasicInlineMarkdown(escapeHtml(text.slice(last, m.index))));
97
+ const raw = m[0];
98
+ const trimmed = raw.replace(/[.,;:!?)\]]+$/u, "");
99
+ const rest = raw.slice(trimmed.length);
100
+ if (isAllowedWalleeBrowserUrl(trimmed)) {
101
+ parts.push(walleeOutputAnchor(trimmed, escapeHtml(trimmed)));
102
+ if (rest) {
103
+ parts.push(applyBasicInlineMarkdown(escapeHtml(rest)));
104
+ }
105
+ }
106
+ else {
107
+ parts.push(applyBasicInlineMarkdown(escapeHtml(raw)));
108
+ }
109
+ last = m.index + raw.length;
110
+ }
111
+ parts.push(applyBasicInlineMarkdown(escapeHtml(text.slice(last))));
112
+ return parts.join("");
113
+ }
114
+ /**
115
+ * Like {@link markdownToHtml} but inline http(s) URLs and markdown links `[label](https://…)` render as
116
+ * Wallee embedded-browser anchors (`wallee-output-link` + `data-wallee-url`).
117
+ */
118
+ export function markdownToWalleeOutputHtml(markdown) {
119
+ const normalized = markdown.replace(/\r\n/g, "\n");
120
+ const blocks = normalized.split(/\n\n+/);
121
+ const html = [];
122
+ for (const block of blocks) {
123
+ const trimmed = block.trim();
124
+ if (!trimmed) {
125
+ continue;
126
+ }
127
+ if (trimmed.startsWith("```") && trimmed.endsWith("```")) {
128
+ const lines = trimmed.split("\n");
129
+ const language = lines[0]?.slice(3).trim();
130
+ const code = lines.slice(1, -1).join("\n");
131
+ html.push(`<pre class="ah-code"><code${language ? ` data-language="${escapeHtml(language)}"` : ""}>${escapeHtml(code)}</code></pre>`);
132
+ continue;
133
+ }
134
+ if (/^#{1,6}\s/.test(trimmed)) {
135
+ const match = trimmed.match(/^(#{1,6})\s+(.*)$/);
136
+ const level = match?.[1].length ?? 1;
137
+ const content = linkifyPlainTextSegmentWithWalleeMarkdown(match?.[2] ?? trimmed);
138
+ html.push(`<h${level}>${content}</h${level}>`);
139
+ continue;
140
+ }
141
+ if (trimmed.split("\n").every((line) => /^[-*]\s+/.test(line))) {
142
+ const items = trimmed
143
+ .split("\n")
144
+ .map((line) => line.replace(/^[-*]\s+/, ""))
145
+ .map((line) => `<li>${linkifyPlainTextSegmentWithWalleeMarkdown(line)}</li>`)
146
+ .join("");
147
+ html.push(`<ul>${items}</ul>`);
148
+ continue;
149
+ }
150
+ html.push(`<p>${trimmed
151
+ .split("\n")
152
+ .map((line) => linkifyPlainTextSegmentWithWalleeMarkdown(line))
153
+ .join("<br />")}</p>`);
154
+ }
155
+ return html.join("");
156
+ }
11
157
  function renderInlineMarkdown(text) {
12
158
  const escaped = escapeHtml(text);
13
159
  return escaped
@@ -3,7 +3,7 @@ import { getSkillInheritancePolicy, resolveToolTargets } from "../extensions.js"
3
3
  import { compileModel, compileTool } from "./resource-compilers.js";
4
4
  import { inferAgentCapabilities } from "./support/agent-capabilities.js";
5
5
  import { discoverSkillPaths } from "./support/discovery.js";
6
- import { compileAgentMemories, getRuntimeDefaults, resolvePromptValue, resolveRefId } from "./support/workspace-ref-utils.js";
6
+ import { compileAgentMemories, getRuntimeDefaults, getWorkspaceObject, resolvePromptValue, resolveRefId } from "./support/workspace-ref-utils.js";
7
7
  const WORKSPACE_BOUNDARY_GUIDANCE = "Keep repository and file exploration bounded to the current workspace root unless the user explicitly asks for broader host or filesystem access. " +
8
8
  "Do not inspect absolute paths outside the workspace, system directories, or unrelated repos by default. " +
9
9
  "Prefer workspace-local tools, relative paths, and the current repository checkout when analyzing code.";
@@ -133,15 +133,50 @@ function resolveBackendConfig(agent) {
133
133
  : undefined;
134
134
  return backendConfig ? { config: backendConfig } : undefined;
135
135
  }
136
- function resolveStoreConfig(agent) {
136
+ function isRefConfig(value) {
137
+ if (typeof value?.ref !== "string" || value.ref.trim().length === 0) {
138
+ return false;
139
+ }
140
+ return Object.keys(value).every((key) => key === "ref");
141
+ }
142
+ function materializeWorkspaceObjectConfig(refs, ref, allowedKinds, ownerLabel) {
143
+ const object = getWorkspaceObject(refs, ref);
144
+ if (!object) {
145
+ throw new Error(`${ownerLabel} references missing object ${ref}`);
146
+ }
147
+ if (!allowedKinds.includes(object.kind)) {
148
+ throw new Error(`${ownerLabel} references ${ref}, but expected one of: ${allowedKinds.join(", ")}`);
149
+ }
150
+ const { id: _id, kind: _kind, ...config } = object.value;
151
+ if (object.kind === "store") {
152
+ const storeKind = typeof config.storeKind === "string" ? config.storeKind : undefined;
153
+ const { storeKind: _storeKind, ...rest } = config;
154
+ return storeKind ? { kind: storeKind, ...rest } : config;
155
+ }
156
+ if (object.kind === "checkpointer" || object.kind === "file-checkpointer" || object.kind === "sqlite-saver") {
157
+ const checkpointerKind = typeof config.checkpointerKind === "string" ? config.checkpointerKind : undefined;
158
+ const { checkpointerKind: _checkpointerKind, ...rest } = config;
159
+ return checkpointerKind ? { kind: checkpointerKind, ...rest } : config;
160
+ }
161
+ return config;
162
+ }
163
+ function resolveStoreConfig(agent, refs) {
137
164
  const inlineStore = typeof agent.deepAgentConfig?.store === "object" && agent.deepAgentConfig.store
138
165
  ? agent.deepAgentConfig.store
139
166
  : typeof agent.langchainAgentConfig?.store === "object" && agent.langchainAgentConfig.store
140
167
  ? agent.langchainAgentConfig.store
141
168
  : undefined;
142
- return inlineStore ? { config: inlineStore } : undefined;
169
+ if (!inlineStore) {
170
+ return undefined;
171
+ }
172
+ if (isRefConfig(inlineStore)) {
173
+ return {
174
+ config: materializeWorkspaceObjectConfig(refs, inlineStore.ref, ["store"], `Agent ${agent.id} store`),
175
+ };
176
+ }
177
+ return { config: inlineStore };
143
178
  }
144
- function resolveCheckpointerConfig(agent) {
179
+ function resolveCheckpointerConfig(agent, refs) {
145
180
  const inlineAgentCheckpointer = typeof agent.deepAgentConfig?.checkpointer === "object" && agent.deepAgentConfig.checkpointer
146
181
  ? agent.deepAgentConfig.checkpointer
147
182
  : typeof agent.deepAgentConfig?.checkpointer === "boolean"
@@ -151,7 +186,18 @@ function resolveCheckpointerConfig(agent) {
151
186
  : typeof agent.langchainAgentConfig?.checkpointer === "boolean"
152
187
  ? agent.langchainAgentConfig.checkpointer
153
188
  : undefined;
154
- return inlineAgentCheckpointer !== undefined ? { config: inlineAgentCheckpointer } : undefined;
189
+ if (inlineAgentCheckpointer === undefined) {
190
+ return undefined;
191
+ }
192
+ if (typeof inlineAgentCheckpointer === "boolean") {
193
+ return { config: inlineAgentCheckpointer };
194
+ }
195
+ if (isRefConfig(inlineAgentCheckpointer)) {
196
+ return {
197
+ config: materializeWorkspaceObjectConfig(refs, inlineAgentCheckpointer.ref, ["checkpointer", "file-checkpointer", "sqlite-saver"], `Agent ${agent.id} checkpointer`),
198
+ };
199
+ }
200
+ return { config: inlineAgentCheckpointer };
155
201
  }
156
202
  export function compileBinding(workspaceRoot, agent, agents, referencedSubagentIds, refs, models, tools) {
157
203
  const internalSubagent = referencedSubagentIds.has(agent.id);
@@ -160,8 +206,8 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
160
206
  const compiledAgentMemory = compileAgentMemories(workspaceRoot, agent.memorySources);
161
207
  const compiledAgentModel = requireModel(models, agent.modelRef || (internalSubagent ? "model/default" : ""), agent.id);
162
208
  const backend = resolveBackendConfig(agent);
163
- const store = resolveStoreConfig(agent);
164
- const checkpointer = resolveCheckpointerConfig(agent);
209
+ const store = resolveStoreConfig(agent, refs);
210
+ const checkpointer = resolveCheckpointerConfig(agent, refs);
165
211
  const runRoot = typeof agent.runRoot === "string" && agent.runRoot.trim().length > 0
166
212
  ? path.resolve(workspaceRoot, agent.runRoot)
167
213
  : typeof runtimeDefaults?.runRoot === "string" && runtimeDefaults.runRoot.trim().length > 0
@@ -497,9 +497,19 @@ async function readNamedModelItems(root) {
497
497
  }
498
498
  const parsedDocuments = parseAllDocuments(await readYamlOrJson(filePath));
499
499
  for (const parsedDocument of parsedDocuments) {
500
- const items = normalizeNamedResourceSpec(parsedDocument.toJSON(), "model");
501
- for (const item of items) {
502
- records.push({ item: normalizeYamlItem(item), sourcePath: filePath });
500
+ const document = parsedDocument.toJSON();
501
+ const catalogItems = normalizeNamedResourceSpec(document, "model");
502
+ if (catalogItems.length > 0) {
503
+ for (const item of catalogItems) {
504
+ records.push({ item: normalizeYamlItem(item), sourcePath: filePath });
505
+ }
506
+ continue;
507
+ }
508
+ for (const item of await objectItemsFromDocument(document, filePath)) {
509
+ const normalized = normalizeYamlItem(item);
510
+ if (normalized.kind === "model" && typeof normalized.id === "string" && normalized.id.trim()) {
511
+ records.push({ item: normalized, sourcePath: filePath });
512
+ }
503
513
  }
504
514
  }
505
515
  }
@@ -5,3 +5,5 @@ export declare function isDelegationCapableAgent(agent: ParsedAgentObject): bool
5
5
  export declare function isMemoryCapableAgent(agent: ParsedAgentObject): boolean;
6
6
  export declare function isDelegationCapableBinding(binding: CompiledAgentBinding): boolean;
7
7
  export declare function isMemoryCapableBinding(binding: CompiledAgentBinding): boolean;
8
+ export declare function getAgentSystemPrompt(agent: ParsedAgentObject): string | undefined;
9
+ export declare function hasAgentSystemPrompt(agent: ParsedAgentObject): boolean;
@@ -28,3 +28,14 @@ export function isDelegationCapableBinding(binding) {
28
28
  export function isMemoryCapableBinding(binding) {
29
29
  return inferBindingCapabilities(binding).memory === true;
30
30
  }
31
+ export function getAgentSystemPrompt(agent) {
32
+ const deepagentPrompt = typeof agent.deepAgentConfig?.systemPrompt === "string" ? agent.deepAgentConfig.systemPrompt.trim() : "";
33
+ if (deepagentPrompt) {
34
+ return deepagentPrompt;
35
+ }
36
+ const langchainPrompt = typeof agent.langchainAgentConfig?.systemPrompt === "string" ? agent.langchainAgentConfig.systemPrompt.trim() : "";
37
+ return langchainPrompt || undefined;
38
+ }
39
+ export function hasAgentSystemPrompt(agent) {
40
+ return typeof getAgentSystemPrompt(agent) === "string";
41
+ }
@@ -1,8 +1,5 @@
1
- import { isDelegationCapableAgent, isMemoryCapableAgent } from "./support/agent-capabilities.js";
1
+ import { hasAgentSystemPrompt, isDelegationCapableAgent, isMemoryCapableAgent, } from "./support/agent-capabilities.js";
2
2
  const allowedExecutionModes = new Set(["deepagent", "langchain-v1"]);
3
- function hasPromptContent(value) {
4
- return typeof value === "string" && value.trim().length > 0;
5
- }
6
3
  function validateCheckpointerConfig(agent) {
7
4
  const checkpointer = (typeof agent.deepAgentConfig?.checkpointer === "object" && agent.deepAgentConfig.checkpointer) ||
8
5
  (typeof agent.deepAgentConfig?.checkpointer === "boolean" ? agent.deepAgentConfig.checkpointer : undefined) ||
@@ -87,8 +84,8 @@ export function validateTopology(agents) {
87
84
  if (!isDelegationCapableAgent(agent)) {
88
85
  throw new Error(`Subagent ${agent.id} must use a delegation-capable backend`);
89
86
  }
90
- if (!hasPromptContent(agent.deepAgentConfig?.systemPrompt)) {
91
- throw new Error(`Subagent ${agent.id} requires deepagent.systemPrompt`);
87
+ if (!hasAgentSystemPrompt(agent)) {
88
+ throw new Error(`Subagent ${agent.id} requires systemPrompt`);
92
89
  }
93
90
  }
94
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.43",
3
+ "version": "0.0.44",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
@@ -26,6 +26,11 @@
26
26
  "types": "./dist/tools.d.ts",
27
27
  "import": "./dist/tools.js",
28
28
  "default": "./dist/tools.js"
29
+ },
30
+ "./presentation": {
31
+ "types": "./dist/presentation.d.ts",
32
+ "import": "./dist/presentation.js",
33
+ "default": "./dist/presentation.js"
29
34
  }
30
35
  },
31
36
  "dependencies": {
@@ -50,8 +55,9 @@
50
55
  "scripts": {
51
56
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
52
57
  "check": "tsc -p tsconfig.json --noEmit",
53
- "test": "vitest run test/public-api.test.ts test/resource-optional-provider.test.ts test/resource-isolation.test.ts test/stock-research-app-load-harness.test.ts test/stock-research-app-run.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts test/readme.test.ts test/runtime-adapter-regressions.test.ts test/runtime-recovery.test.ts test/tool-extension-gaps.test.ts test/checkpoint-maintenance.test.ts test/llamaindex-dependency-compat.test.ts test/skill-standard.test.ts test/routing-config.test.ts test/workspace-compat-regressions.test.ts test/upstream-compat-regressions.test.ts",
58
+ "test": "vitest run test/public-api.test.ts test/resource-optional-provider.test.ts test/resource-isolation.test.ts test/stock-research-app-load-harness.test.ts test/stock-research-app-run.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts test/readme.test.ts test/runtime-adapter-regressions.test.ts test/runtime-recovery.test.ts test/tool-extension-gaps.test.ts test/checkpoint-maintenance.test.ts test/llamaindex-dependency-compat.test.ts test/skill-standard.test.ts test/routing-config.test.ts test/workspace-compat-regressions.test.ts test/upstream-compat-regressions.test.ts test/embedded-browser-bookmarks.test.ts test/presentation-wallee.test.ts",
54
59
  "test:real-providers": "vitest run test/real-provider-harness.test.ts",
60
+ "test:integration": "npm run build && node scripts/integration-wallee-browser.mjs",
55
61
  "release:prepare": "npm version patch --no-git-tag-version && node ./scripts/sync-example-version.mjs",
56
62
  "release:pack": "npm pack --dry-run",
57
63
  "release:publish": "npm publish --access public --registry https://registry.npmjs.org/"