@botbotgo/agent-harness 0.0.49 → 0.0.51

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
@@ -6,16 +6,16 @@
6
6
 
7
7
  It is not a new agent framework. It is the runtime layer around LangChain v1 and DeepAgents that turns one workspace into one operable application runtime.
8
8
 
9
- The product boundary is:
9
+ The product boundary is simple:
10
10
 
11
11
  - LangChain v1 and DeepAgents own agent execution semantics
12
- - `agent-harness` owns application runtime semantics
12
+ - `agent-harness` owns application-level orchestration and lifecycle management
13
13
 
14
14
  That means:
15
15
 
16
16
  - public API stays small
17
- - complex assembly and policy live in YAML
18
- - runtime lifecycle stays stable even if the backend implementation changes
17
+ - complex setup and operating policy live in YAML
18
+ - runtime lifecycle stays stable even if backend implementations change
19
19
 
20
20
  What the runtime provides:
21
21
 
@@ -81,10 +81,12 @@ try {
81
81
  - YAML-defined host routing and runtime policy
82
82
  - LangChain v1 and DeepAgents backend adaptation
83
83
  - Auto-discovered local tools and SKILL packages
84
+ - provider-native tools, MCP tools, and workspace-local tool modules
85
+ - persisted threads, runs, approvals, lifecycle events, and queued runs
86
+ - runtime-managed recovery and checkpoint maintenance
87
+ - structured output and multimodal content preservation in run results
84
88
  - MCP bridge support for agent-declared MCP servers
85
89
  - MCP server support for exposing harness tools outward
86
- - Persisted threads, runs, approvals, lifecycle events, and queued runs
87
- - Runtime-managed recovery and checkpoint maintenance
88
90
 
89
91
  ## How To Use
90
92
 
@@ -120,7 +122,7 @@ const result = await run(runtime, {
120
122
  });
121
123
  ```
122
124
 
123
- Each run creates or continues a persisted thread and returns `threadId`, `runId`, `state`, and the final user-facing output text.
125
+ `run(runtime, { ... })` creates or continues a persisted thread and returns `threadId`, `runId`, `state`, and a simple text `output`. When upstream returns richer output, the runtime also preserves `outputContent`, `contentBlocks`, and `structuredResponse` without making the basic API larger.
124
126
 
125
127
  Use `invocation` as the runtime-facing request envelope:
126
128
 
@@ -149,6 +151,9 @@ const result = await run(runtime, {
149
151
  onChunk(chunk) {
150
152
  process.stdout.write(chunk);
151
153
  },
154
+ onContentBlocks(blocks) {
155
+ console.log(blocks);
156
+ },
152
157
  onEvent(event) {
153
158
  console.log(event.eventType, event.payload);
154
159
  },
@@ -196,11 +201,11 @@ metadata:
196
201
  spec:
197
202
  execution:
198
203
  backend: deepagent
199
- modelRef: model/default
200
- mcpServers:
201
- - name: browser
202
- command: node
203
- args: ["./mcp-browser-server.mjs"]
204
+ modelRef: model/default
205
+ mcpServers:
206
+ - name: browser
207
+ command: node
208
+ args: ["./mcp-browser-server.mjs"]
204
209
  ```
205
210
 
206
211
  The runtime discovers MCP tools, filters them through agent configuration, and exposes them like other tools.
@@ -244,8 +249,6 @@ Core workspace files:
244
249
  - `resources/tools/`
245
250
  - `resources/skills/`
246
251
 
247
- ### Client-Configurable YAML Reference
248
-
249
252
  There are three configuration layers:
250
253
 
251
254
  - runtime policy in `config/workspace.yaml`
@@ -344,6 +347,13 @@ Use this file for named MCP server presets.
344
347
 
345
348
  Agents are always declared with `kind: Agent` and `spec.execution.backend`.
346
349
 
350
+ Use two nested sections inside each agent:
351
+
352
+ - `spec.runtime` for harness-owned runtime placement such as `spec.runtime.runRoot`
353
+ - `spec.execution` for upstream execution semantics and adapter-facing config
354
+
355
+ This keeps the public product model small while letting LangChain v1 and DeepAgents concepts pass through with minimal translation.
356
+
347
357
  Example lightweight host:
348
358
 
349
359
  ```yaml
@@ -354,10 +364,11 @@ metadata:
354
364
  spec:
355
365
  execution:
356
366
  backend: langchain-v1
357
- modelRef: model/default
358
- checkpointer:
359
- ref: checkpointer/default
360
- systemPrompt: Answer simple requests directly.
367
+ modelRef: model/default
368
+ config:
369
+ checkpointer:
370
+ ref: checkpointer/default
371
+ systemPrompt: Answer simple requests directly.
361
372
  ```
362
373
 
363
374
  Example main execution host:
@@ -370,21 +381,22 @@ metadata:
370
381
  spec:
371
382
  execution:
372
383
  backend: deepagent
373
- modelRef: model/default
374
- memory:
375
- - path: config/agent-context.md
376
- store:
377
- ref: store/default
378
- checkpointer:
379
- ref: checkpointer/default
380
- backend:
381
- kind: CompositeBackend
382
- state:
383
- kind: VfsSandbox
384
- timeout: 600
385
- routes:
386
- /memories/:
387
- kind: StoreBackend
384
+ modelRef: model/default
385
+ memory:
386
+ - path: config/agent-context.md
387
+ config:
388
+ store:
389
+ ref: store/default
390
+ checkpointer:
391
+ ref: checkpointer/default
392
+ backend:
393
+ kind: CompositeBackend
394
+ state:
395
+ kind: VfsSandbox
396
+ timeout: 600
397
+ routes:
398
+ /memories/:
399
+ kind: StoreBackend
388
400
  ```
389
401
 
390
402
  Client-configurable agent fields include:
@@ -392,19 +404,29 @@ Client-configurable agent fields include:
392
404
  - `metadata.name`
393
405
  - `metadata.description`
394
406
  - `spec.execution.backend`
395
- - `spec.modelRef`
396
- - `spec.systemPrompt`
397
- - `spec.tools`
398
- - `spec.skills`
399
- - `spec.memory`
400
- - `spec.checkpointer`
401
- - `spec.store`
402
- - `spec.backend`
403
- - `spec.middleware`
404
- - `spec.subagents`
405
- - `spec.mcpServers`
406
- - `spec.responseFormat`
407
- - `spec.contextSchema`
407
+ - `spec.runtime.runRoot`
408
+ - `spec.execution.modelRef`
409
+ - `spec.execution.tools`
410
+ - `spec.execution.skills`
411
+ - `spec.execution.memory`
412
+ - `spec.execution.subagents`
413
+ - `spec.execution.mcpServers`
414
+ - `spec.execution.config.systemPrompt`
415
+ - `spec.execution.config.checkpointer`
416
+ - `spec.execution.config.store`
417
+ - `spec.execution.config.backend`
418
+ - `spec.execution.config.middleware`
419
+ - `spec.execution.config.responseFormat`
420
+ - `spec.execution.config.contextSchema`
421
+ - `spec.execution.config.stateSchema`
422
+ - `spec.execution.config.interruptOn`
423
+ - `spec.execution.config.filesystem`
424
+ - `spec.execution.config.taskDescription`
425
+ - `spec.execution.config.generalPurposeAgent`
426
+ - `spec.execution.config.includeAgentName`
427
+ - `spec.execution.config.version`
428
+
429
+ For backend-specific agent options, prefer passing the upstream concept directly inside `spec.execution.config`. The loader keeps a small stable product shape, but it also preserves adapter-facing passthrough fields so new LangChain v1 or DeepAgents parameters can flow into adapters without expanding the public API surface.
408
430
 
409
431
  ### `resources/`
410
432
 
@@ -9,41 +9,43 @@ metadata:
9
9
  # agent-harness feature: human-readable summary for inventory and UI.
10
10
  description: Manual low-latency host for direct answers and lightweight inventory lookup. Do not use it as the default executor for tool-heavy or specialist-shaped tasks.
11
11
  spec:
12
- execution:
13
- # Current backend adapter for this host profile.
14
- backend: langchain-v1
12
+ runtime: {}
15
13
  # =====================
16
14
  # Runtime Agent Features
17
15
  # =====================
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
- modelRef: model/default
22
- # Runtime execution feature: checkpointer config passed into the selected backend adapter.
23
- # Even the lightweight direct path can benefit from resumable state during interactive use.
24
- # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
25
- # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
26
- checkpointer:
27
- ref: checkpointer/default
28
- # Upstream execution feature: system prompt for the lightweight direct-response host.
29
- # This prompt should keep the agent focused on:
30
- # - answering simple requests in one turn
31
- # - staying lightweight instead of planning or orchestrating
32
- # - avoiding specialist-style decomposition unless the caller explicitly switches agents
33
- #
34
- # The direct host is intentionally narrower than the orchestra host:
35
- # - `direct` is optimized for latency and straightforward completion
36
- # - `orchestra` is optimized for multi-step work, tools, delegation, and specialist use
37
- #
38
- # Keep this prompt biased toward concise, self-contained answers. If richer routing policy is
39
- # needed for choosing between host agents, configure that separately via `Runtime.spec.routing`
40
- # rather than overloading the direct host prompt with classifier behavior.
41
- systemPrompt: |-
42
- You are the direct agent.
16
+ execution:
17
+ # Current backend adapter for this host profile.
18
+ backend: langchain-v1
19
+ # Upstream execution feature: model ref for the underlying LLM used by the direct-response agent.
20
+ # This should point at a cheap, fast, general-purpose chat model, because `direct` is intended
21
+ # to be the low-latency path for simple requests.
22
+ modelRef: model/default
23
+ config:
24
+ # Runtime execution feature: checkpointer config passed into the selected backend adapter.
25
+ # Even the lightweight direct path can benefit from resumable state during interactive use.
26
+ # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
27
+ # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
28
+ checkpointer:
29
+ ref: checkpointer/default
30
+ # Upstream execution feature: system prompt for the lightweight direct-response host.
31
+ # This prompt should keep the agent focused on:
32
+ # - answering simple requests in one turn
33
+ # - staying lightweight instead of planning or orchestrating
34
+ # - avoiding specialist-style decomposition unless the caller explicitly switches agents
35
+ #
36
+ # The direct host is intentionally narrower than the orchestra host:
37
+ # - `direct` is optimized for latency and straightforward completion
38
+ # - `orchestra` is optimized for multi-step work, tools, delegation, and specialist use
39
+ #
40
+ # Keep this prompt biased toward concise, self-contained answers. If richer routing policy is
41
+ # needed for choosing between host agents, configure that separately via `Runtime.spec.routing`
42
+ # rather than overloading the direct host prompt with classifier behavior.
43
+ systemPrompt: |-
44
+ You are the direct agent.
43
45
 
44
- This is a manual low-latency host.
45
- Answer simple requests directly.
46
- Keep the path lightweight.
47
- Do not delegate.
48
- Do not perform broad multi-step execution.
49
- Do not behave like the default execution host.
46
+ This is a manual low-latency host.
47
+ Answer simple requests directly.
48
+ Keep the path lightweight.
49
+ Do not delegate.
50
+ Do not perform broad multi-step execution.
51
+ Do not behave like the default execution host.
@@ -9,86 +9,88 @@ metadata:
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.
11
11
  spec:
12
- execution:
13
- # Current backend adapter for this host profile.
14
- backend: deepagent
12
+ runtime: {}
15
13
  # =====================
16
14
  # Runtime Agent Features
17
15
  # =====================
18
- # Upstream execution feature: model ref for the underlying LLM used by this execution host.
19
- modelRef: model/default
20
- # Runtime execution feature: checkpointer config passed into the selected backend adapter.
21
- # This persists resumable graph state for this agent.
22
- # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
23
- # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
24
- checkpointer:
25
- # ref: checkpointer/sqlite
26
- ref: checkpointer/default
27
- memory:
28
- # Upstream execution feature: bootstrap memory sources supplied to the selected backend at construction time.
29
- # These paths resolve relative to the workspace root unless they are already absolute.
30
- # Treat this as agent-owned startup context, not as a dynamic long-term memory sink:
31
- # - keep `systemPrompt` for stable role, boundaries, and hard behavioral rules
32
- # - use `memory:` for stable project knowledge, operating conventions, and shared or agent-specific context files
33
- # - use `/memories/*` via the backend/store below for durable knowledge learned from prior runs
34
- # - use the harness checkpointer for resumable graph state for an in-flight run
35
- # Updating these files changes future agent constructions, but they are still bootstrap inputs rather than
36
- # self-updating runtime memory.
37
- - path: config/agent-context.md
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
- # Available `kind` options in this harness: `FileStore`, `InMemoryStore`, `RedisStore`, `PostgresStore`.
41
- store:
42
- ref: store/default
43
- # Upstream execution feature: backend config passed into the selected backend adapter.
44
- # This directly defines the backend topology for this agent:
45
- # - workspace execution uses a lightweight VFS sandbox
46
- # - long-term memory under `/memories/*` uses `StoreBackend`
47
- # - `CompositeBackend` composes those backend instances together
48
- # The harness also injects a persistent file-backed store and a file-backed checkpointer so that
49
- # `/memories/*` and resumable run state survive restarts in the default setup.
50
- # Available top-level `kind` options in this harness: `CompositeBackend`, `StateBackend`, `StoreBackend`.
51
- backend:
52
- kind: CompositeBackend
53
- state:
54
- # Available state backend `kind` options today: `StateBackend`, `LocalShellBackend`, `VfsSandbox`.
55
- kind: VfsSandbox
56
- timeout: 600
57
- routes:
58
- /memories/:
59
- # Available route backend `kind` options today: `StoreBackend`.
60
- kind: StoreBackend
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
63
- # agent's durable role, priorities, and behavioral guardrails rather than bulky project facts.
64
- systemPrompt: |-
65
- You are the orchestra agent.
16
+ execution:
17
+ # Current backend adapter for this host profile.
18
+ backend: deepagent
19
+ # Upstream execution feature: model ref for the underlying LLM used by this execution host.
20
+ modelRef: model/default
21
+ memory:
22
+ # Upstream execution feature: bootstrap memory sources supplied to the selected backend at construction time.
23
+ # These paths resolve relative to the workspace root unless they are already absolute.
24
+ # Treat this as agent-owned startup context, not as a dynamic long-term memory sink:
25
+ # - keep `systemPrompt` for stable role, boundaries, and hard behavioral rules
26
+ # - use `memory:` for stable project knowledge, operating conventions, and shared or agent-specific context files
27
+ # - use `/memories/*` via the backend/store below for durable knowledge learned from prior runs
28
+ # - use the harness checkpointer for resumable graph state for an in-flight run
29
+ # Updating these files changes future agent constructions, but they are still bootstrap inputs rather than
30
+ # self-updating runtime memory.
31
+ - path: config/agent-context.md
32
+ config:
33
+ # Runtime execution feature: checkpointer config passed into the selected backend adapter.
34
+ # This persists resumable graph state for this agent.
35
+ # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
36
+ # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
37
+ checkpointer:
38
+ # ref: checkpointer/sqlite
39
+ ref: checkpointer/default
40
+ # Upstream execution feature: store config passed into the selected backend adapter.
41
+ # In the default deepagent adapter this is the LangGraph store used by `StoreBackend` routes.
42
+ # Available `kind` options in this harness: `FileStore`, `InMemoryStore`, `RedisStore`, `PostgresStore`.
43
+ store:
44
+ ref: store/default
45
+ # Upstream execution feature: backend config passed into the selected backend adapter.
46
+ # This directly defines the backend topology for this agent:
47
+ # - workspace execution uses a lightweight VFS sandbox
48
+ # - long-term memory under `/memories/*` uses `StoreBackend`
49
+ # - `CompositeBackend` composes those backend instances together
50
+ # The harness also injects a persistent file-backed store and a file-backed checkpointer so that
51
+ # `/memories/*` and resumable run state survive restarts in the default setup.
52
+ # Available top-level `kind` options in this harness: `CompositeBackend`, `StateBackend`, `StoreBackend`.
53
+ backend:
54
+ kind: CompositeBackend
55
+ state:
56
+ # Available state backend `kind` options today: `StateBackend`, `LocalShellBackend`, `VfsSandbox`.
57
+ kind: VfsSandbox
58
+ timeout: 600
59
+ routes:
60
+ /memories/:
61
+ # Available route backend `kind` options today: `StoreBackend`.
62
+ kind: StoreBackend
63
+ # Upstream execution feature: system prompt for the orchestration host.
64
+ # This becomes the top-level instruction block for the selected execution backend and should hold the
65
+ # agent's durable role, priorities, and behavioral guardrails rather than bulky project facts.
66
+ systemPrompt: |-
67
+ You are the orchestra agent.
66
68
 
67
- You are the default execution host.
68
- Try to finish the request yourself before delegating.
69
- Use your own tools first when they are sufficient.
70
- Use your own skills first when they are sufficient.
71
- Delegate only when a specialist is a clearly better fit or when your own tools and skills are not enough.
72
- If neither you nor any suitable specialist can do the work, say so plainly.
69
+ You are the default execution host.
70
+ Try to finish the request yourself before delegating.
71
+ Use your own tools first when they are sufficient.
72
+ Use your own skills first when they are sufficient.
73
+ Delegate only when a specialist is a clearly better fit or when your own tools and skills are not enough.
74
+ If neither you nor any suitable specialist can do the work, say so plainly.
73
75
 
74
- Do not delegate by reflex.
75
- Do not delegate just because a task has multiple steps.
76
- Do not delegate when a direct answer or a short local tool pass is enough.
77
- Keep the critical path local when immediate progress depends on it; otherwise delegate bounded sidecar work to
78
- the most appropriate specialist.
76
+ Do not delegate by reflex.
77
+ Do not delegate just because a task has multiple steps.
78
+ Do not delegate when a direct answer or a short local tool pass is enough.
79
+ Keep the critical path local when immediate progress depends on it; otherwise delegate bounded sidecar work to
80
+ the most appropriate specialist.
79
81
 
80
- Use your own tools for lightweight discovery, inventory, and context gathering.
81
- Prefer the structured checkout, indexing, retrieval, and inventory tools that are already attached to you over
82
- ad hoc shell work when those tools are sufficient.
83
- Use the attached specialist descriptions as the source of truth for what each specialist is for.
84
- Do not delegate to a specialist whose description does not clearly match the task.
85
- Integrate specialist results into one coherent answer and do not claim checks or evidence you did not obtain.
82
+ Use your own tools for lightweight discovery, inventory, and context gathering.
83
+ Prefer the structured checkout, indexing, retrieval, and inventory tools that are already attached to you over
84
+ ad hoc shell work when those tools are sufficient.
85
+ Use the attached specialist descriptions as the source of truth for what each specialist is for.
86
+ Do not delegate to a specialist whose description does not clearly match the task.
87
+ Integrate specialist results into one coherent answer and do not claim checks or evidence you did not obtain.
86
88
 
87
- When the user asks about available tools, skills, or agents, use the attached inventory tools instead of
88
- inferring from memory.
89
+ When the user asks about available tools, skills, or agents, use the attached inventory tools instead of
90
+ inferring from memory.
89
91
 
90
- Write to `/memories/*` only when the information is durable, reusable across future runs or threads, and likely
91
- to matter again: user preferences, project conventions, confirmed decisions, reusable summaries, and stable
92
- ownership facts are good candidates.
93
- Do not store transient reasoning, temporary plans, scratch work, one-off search results, or intermediate
94
- outputs that can be cheaply recomputed.
92
+ Write to `/memories/*` only when the information is durable, reusable across future runs or threads, and likely
93
+ to matter again: user preferences, project conventions, confirmed decisions, reusable summaries, and stable
94
+ ownership facts are good candidates.
95
+ Do not store transient reasoning, temporary plans, scratch work, one-off search results, or intermediate
96
+ outputs that can be cheaply recomputed.
@@ -95,7 +95,9 @@ export type LangChainAgentParams = {
95
95
  stateSchema?: unknown;
96
96
  responseFormat?: unknown;
97
97
  contextSchema?: unknown;
98
+ filesystem?: Record<string, unknown>;
98
99
  middleware?: Array<Record<string, unknown>>;
100
+ passthrough?: Record<string, unknown>;
99
101
  subagents?: CompiledSubAgent[];
100
102
  memory?: string[];
101
103
  skills?: string[];
@@ -118,6 +120,7 @@ export type CompiledSubAgent = {
118
120
  responseFormat?: unknown;
119
121
  contextSchema?: unknown;
120
122
  middleware?: Array<Record<string, unknown>>;
123
+ passthrough?: Record<string, unknown>;
121
124
  };
122
125
  export type DeepAgentParams = {
123
126
  model: CompiledModel;
@@ -126,6 +129,7 @@ export type DeepAgentParams = {
126
129
  responseFormat?: unknown;
127
130
  contextSchema?: unknown;
128
131
  middleware?: Array<Record<string, unknown>>;
132
+ passthrough?: Record<string, unknown>;
129
133
  description: string;
130
134
  subagents: CompiledSubAgent[];
131
135
  interruptOn?: Record<string, boolean | object>;
@@ -257,6 +261,9 @@ export type RunResult = {
257
261
  state: RunState;
258
262
  output: string;
259
263
  finalMessageText?: string;
264
+ outputContent?: unknown;
265
+ contentBlocks?: unknown[];
266
+ structuredResponse?: unknown;
260
267
  interruptContent?: string;
261
268
  agentId?: string;
262
269
  approvalId?: string;
@@ -267,6 +274,7 @@ export type RunResult = {
267
274
  };
268
275
  export type RunListeners = {
269
276
  onChunk?: (chunk: string) => void | Promise<void>;
277
+ onContentBlocks?: (blocks: unknown[]) => void | Promise<void>;
270
278
  onEvent?: (event: HarnessEvent) => void | Promise<void>;
271
279
  onReasoning?: (chunk: string) => void | Promise<void>;
272
280
  onStep?: (step: string) => void | Promise<void>;
@@ -318,6 +326,12 @@ export type HarnessStreamItem = {
318
326
  runId: string;
319
327
  agentId: string;
320
328
  content: string;
329
+ } | {
330
+ type: "content-blocks";
331
+ threadId: string;
332
+ runId: string;
333
+ agentId: string;
334
+ contentBlocks: unknown[];
321
335
  } | {
322
336
  type: "reasoning";
323
337
  threadId: string;
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.48";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.50";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.48";
1
+ export const AGENT_HARNESS_VERSION = "0.0.50";
@@ -1,18 +1,4 @@
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;
16
2
  export declare function markdownToHtml(markdown: string): string;
17
3
  export declare function markdownToConsole(markdown: string): string;
18
4
  export declare function renderTemplate(data: Record<string, unknown>, template: string): string;
@@ -8,152 +8,6 @@ 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
- }
157
11
  function renderInlineMarkdown(text) {
158
12
  const escaped = escapeHtml(text);
159
13
  return escaped
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import { Command, MemorySaver } from "@langchain/langgraph";
2
3
  import { tool as createLangChainTool } from "@langchain/core/tools";
3
4
  import { createDeepAgent, createMemoryMiddleware, createSkillsMiddleware, createSubAgentMiddleware, FilesystemBackend, } from "deepagents";
@@ -9,7 +10,7 @@ import { ChatOpenAI } from "@langchain/openai";
9
10
  import { tools as openAIProviderTools } from "@langchain/openai";
10
11
  import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain";
11
12
  import { z } from "zod";
12
- import { extractEmptyAssistantMessageFailure, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
13
+ import { extractEmptyAssistantMessageFailure, extractContentBlocks, extractOutputContent, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
13
14
  import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, extractReasoningStreamOutput, extractStateStreamOutput, extractVisibleStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
14
15
  import { wrapToolForExecution } from "./tool-hitl.js";
15
16
  import { resolveDeclaredMiddleware } from "./declared-middleware.js";
@@ -475,10 +476,20 @@ export class AgentRuntimeAdapter {
475
476
  return this.compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
476
477
  }
477
478
  resolveFilesystemBackend(binding) {
479
+ const filesystemConfig = getBindingLangChainParams(binding)?.filesystem;
480
+ const configuredRootDir = typeof filesystemConfig?.rootDir === "string" && filesystemConfig.rootDir.trim().length > 0
481
+ ? filesystemConfig.rootDir
482
+ : undefined;
483
+ const workspaceRoot = binding.harnessRuntime.workspaceRoot;
484
+ const rootDir = configuredRootDir
485
+ ? (path.isAbsolute(configuredRootDir) ? configuredRootDir : path.resolve(workspaceRoot ?? process.cwd(), configuredRootDir))
486
+ : workspaceRoot ?? process.cwd();
478
487
  return new FilesystemBackend({
479
- rootDir: "/",
480
- virtualMode: false,
481
- maxFileSizeMb: 10,
488
+ rootDir,
489
+ virtualMode: filesystemConfig?.virtualMode === true,
490
+ maxFileSizeMb: typeof filesystemConfig?.maxFileSizeMb === "number" && Number.isFinite(filesystemConfig.maxFileSizeMb)
491
+ ? filesystemConfig.maxFileSizeMb
492
+ : 10,
482
493
  });
483
494
  }
484
495
  async resolveLangChainAutomaticMiddleware(binding) {
@@ -556,6 +567,7 @@ export class AgentRuntimeAdapter {
556
567
  async resolveSubagents(subagents, binding) {
557
568
  return Promise.all(subagents.map(async (subagent) => ({
558
569
  ...subagent,
570
+ ...(subagent.passthrough ?? {}),
559
571
  model: subagent.model ? (await this.resolveModel(subagent.model)) : undefined,
560
572
  tools: subagent.tools ? this.resolveTools(subagent.tools) : undefined,
561
573
  interruptOn: this.compileInterruptOn(subagent.tools ?? [], subagent.interruptOn),
@@ -578,6 +590,7 @@ export class AgentRuntimeAdapter {
578
590
  throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
579
591
  }
580
592
  return createAgent({
593
+ ...(params.passthrough ?? {}),
581
594
  model: model,
582
595
  tools: tools,
583
596
  systemPrompt: params.systemPrompt,
@@ -598,6 +611,7 @@ export class AgentRuntimeAdapter {
598
611
  throw new Error(`Agent ${binding.agent.id} has no runnable params`);
599
612
  }
600
613
  const deepAgentConfig = {
614
+ ...(params.passthrough ?? {}),
601
615
  model: (await this.resolveModel(params.model)),
602
616
  tools: this.resolveTools(params.tools, binding),
603
617
  systemPrompt: params.systemPrompt,
@@ -672,6 +686,9 @@ export class AgentRuntimeAdapter {
672
686
  }
673
687
  const output = visibleOutput || synthesizedOutput || toolFallback || JSON.stringify(result, null, 2);
674
688
  const finalMessageText = sanitizeVisibleText(output);
689
+ const outputContent = extractOutputContent(result);
690
+ const contentBlocks = extractContentBlocks(result);
691
+ const structuredResponse = result.structuredResponse;
675
692
  return {
676
693
  threadId,
677
694
  runId,
@@ -680,8 +697,13 @@ export class AgentRuntimeAdapter {
680
697
  interruptContent,
681
698
  output: finalMessageText,
682
699
  finalMessageText,
700
+ ...(outputContent !== undefined ? { outputContent } : {}),
701
+ ...(contentBlocks.length > 0 ? { contentBlocks } : {}),
702
+ ...(structuredResponse !== undefined ? { structuredResponse } : {}),
683
703
  metadata: {
684
- ...(result.structuredResponse !== undefined ? { structuredResponse: result.structuredResponse } : {}),
704
+ ...(structuredResponse !== undefined ? { structuredResponse } : {}),
705
+ ...(outputContent !== undefined ? { outputContent } : {}),
706
+ ...(contentBlocks.length > 0 ? { contentBlocks } : {}),
685
707
  ...(asRecord(result.files) ? { files: asRecord(result.files) } : {}),
686
708
  ...(this.buildStateSnapshot(result) ? { stateSnapshot: this.buildStateSnapshot(result) } : {}),
687
709
  upstreamResult: result,
@@ -1,4 +1,4 @@
1
- import { anthropicPromptCachingMiddleware, contextEditingMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, piiMiddleware, piiRedactionMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolEmulatorMiddleware, toolRetryMiddleware, } from "langchain";
1
+ import { anthropicPromptCachingMiddleware, contextEditingMiddleware, humanInTheLoopMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, piiMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolEmulatorMiddleware, toolRetryMiddleware, } from "langchain";
2
2
  function asMiddlewareConfig(value) {
3
3
  return typeof value === "object" && value !== null && !Array.isArray(value) ? { ...value } : null;
4
4
  }
@@ -77,6 +77,9 @@ export async function resolveDeclaredMiddleware(middlewareConfigs, options) {
77
77
  case "toolEmulator":
78
78
  resolved.push(toolEmulatorMiddleware(runtimeConfig));
79
79
  break;
80
+ case "humanInTheLoop":
81
+ resolved.push(humanInTheLoopMiddleware(runtimeConfig));
82
+ break;
80
83
  case "openAIModeration":
81
84
  resolved.push(openAIModerationMiddleware(runtimeConfig));
82
85
  break;
@@ -89,9 +92,6 @@ export async function resolveDeclaredMiddleware(middlewareConfigs, options) {
89
92
  resolved.push(piiMiddleware(piiType, piiOptions));
90
93
  break;
91
94
  }
92
- case "piiRedaction":
93
- resolved.push(piiRedactionMiddleware(runtimeConfig));
94
- break;
95
95
  case "anthropicPromptCaching":
96
96
  resolved.push(anthropicPromptCachingMiddleware(runtimeConfig));
97
97
  break;
@@ -71,6 +71,7 @@ export declare class AgentHarnessRuntime {
71
71
  private checkpointRefForState;
72
72
  private finalizeContinuedRun;
73
73
  private emitOutputDeltaAndCreateItem;
74
+ private createContentBlocksItem;
74
75
  private emitRunCreated;
75
76
  private setRunStateAndEmit;
76
77
  private requestApprovalAndEmit;
@@ -525,6 +525,15 @@ export class AgentHarnessRuntime {
525
525
  content,
526
526
  };
527
527
  }
528
+ createContentBlocksItem(threadId, runId, agentId, contentBlocks) {
529
+ return {
530
+ type: "content-blocks",
531
+ threadId,
532
+ runId,
533
+ agentId,
534
+ contentBlocks,
535
+ };
536
+ }
528
537
  async emitRunCreated(threadId, runId, payload) {
529
538
  return this.emit(threadId, runId, 1, "run.created", payload);
530
539
  }
@@ -678,6 +687,10 @@ export class AgentHarnessRuntime {
678
687
  await this.notifyListener(listeners.onChunk, item.content);
679
688
  continue;
680
689
  }
690
+ if (item.type === "content-blocks") {
691
+ await this.notifyListener(listeners.onContentBlocks, item.contentBlocks);
692
+ continue;
693
+ }
681
694
  if (item.type === "reasoning") {
682
695
  await this.notifyListener(listeners.onReasoning, item.content);
683
696
  continue;
@@ -890,6 +903,9 @@ export class AgentHarnessRuntime {
890
903
  }
891
904
  if (!assistantOutput) {
892
905
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
906
+ if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
907
+ yield this.createContentBlocksItem(threadId, runId, selectedAgentId, actual.contentBlocks);
908
+ }
893
909
  if (actual.output) {
894
910
  assistantOutput = actual.output;
895
911
  emitted = true;
@@ -949,6 +965,9 @@ export class AgentHarnessRuntime {
949
965
  try {
950
966
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
951
967
  await this.appendAssistantMessage(threadId, runId, actual.output);
968
+ if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
969
+ yield this.createContentBlocksItem(threadId, runId, selectedAgentId, actual.contentBlocks);
970
+ }
952
971
  if (actual.output) {
953
972
  yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
954
973
  }
@@ -6,6 +6,8 @@ export declare function readTextContent(value: unknown): string;
6
6
  export declare function hasToolCalls(value: unknown): boolean;
7
7
  export declare function extractToolFallbackContext(value: unknown): string;
8
8
  export declare function extractVisibleOutput(value: unknown): string;
9
+ export declare function extractOutputContent(value: unknown): unknown;
10
+ export declare function extractContentBlocks(value: unknown): unknown[];
9
11
  export declare function extractEmptyAssistantMessageFailure(value: unknown): string;
10
12
  export declare function isToolCallParseFailure(error: unknown): boolean;
11
13
  export declare const STRICT_TOOL_JSON_INSTRUCTION = "When calling tools, return only the tool call itself. The arguments must be a pure JSON object with no explanatory text before or after it.";
@@ -166,8 +166,8 @@ function extractMessageContent(message) {
166
166
  if (typeof message !== "object" || !message)
167
167
  return "";
168
168
  const typed = message;
169
- if (typeof typed.content === "string")
170
- return typed.content;
169
+ if (typed.content !== undefined)
170
+ return readTextContent(typed.content);
171
171
  if (typeof typed.kwargs === "object" && typed.kwargs) {
172
172
  return readTextContent(typed.kwargs.content);
173
173
  }
@@ -341,6 +341,79 @@ export function extractVisibleOutput(value) {
341
341
  }
342
342
  return "";
343
343
  }
344
+ function isContentBlock(value) {
345
+ return typeof value === "object" && value !== null && typeof value.type === "string";
346
+ }
347
+ function normalizeContentBlocks(value) {
348
+ if (!Array.isArray(value)) {
349
+ return [];
350
+ }
351
+ return value.filter(isContentBlock).map((block) => ({ ...block }));
352
+ }
353
+ function extractStructuredOutputContent(value) {
354
+ if (typeof value !== "object" || !value)
355
+ return undefined;
356
+ const typed = value;
357
+ if (typed.output && typeof typed.output === "object") {
358
+ const nested = extractStructuredOutputContent(typed.output);
359
+ if (nested !== undefined)
360
+ return nested;
361
+ }
362
+ if (typed.content !== undefined) {
363
+ return typed.content;
364
+ }
365
+ if (!Array.isArray(typed.messages)) {
366
+ return undefined;
367
+ }
368
+ for (let index = typed.messages.length - 1; index >= 0; index -= 1) {
369
+ const message = typed.messages[index];
370
+ if (typeof message !== "object" || !message)
371
+ continue;
372
+ const ids = Array.isArray(message.id)
373
+ ? (message.id.filter((item) => typeof item === "string"))
374
+ : [];
375
+ const typeName = ids.at(-1);
376
+ const runtimeType = typeof message._getType === "function"
377
+ ? message._getType()
378
+ : typeof message.getType === "function"
379
+ ? message.getType()
380
+ : undefined;
381
+ if (!(typeName === "AIMessage" || runtimeType === "ai")) {
382
+ continue;
383
+ }
384
+ if (hasToolCalls(message)) {
385
+ continue;
386
+ }
387
+ const directContent = message.content;
388
+ if (directContent !== undefined) {
389
+ return directContent;
390
+ }
391
+ const kwargs = typeof message.kwargs === "object" && message.kwargs
392
+ ? (message.kwargs)
393
+ : undefined;
394
+ if (kwargs?.content !== undefined) {
395
+ return kwargs.content;
396
+ }
397
+ }
398
+ return undefined;
399
+ }
400
+ export function extractOutputContent(value) {
401
+ const content = extractStructuredOutputContent(value);
402
+ if (content !== undefined) {
403
+ return content;
404
+ }
405
+ return undefined;
406
+ }
407
+ export function extractContentBlocks(value) {
408
+ const outputContent = extractOutputContent(value);
409
+ if (outputContent === undefined) {
410
+ return [];
411
+ }
412
+ if (typeof outputContent === "string") {
413
+ return outputContent.trim() ? [{ type: "text", text: outputContent }] : [];
414
+ }
415
+ return normalizeContentBlocks(outputContent);
416
+ }
344
417
  export function extractEmptyAssistantMessageFailure(value) {
345
418
  if (typeof value !== "object" || !value)
346
419
  return "";
@@ -105,6 +105,9 @@ function buildSubagent(agent, workspaceRoot, models, tools, parentSkills, parent
105
105
  responseFormat: agent.deepAgentConfig?.responseFormat,
106
106
  contextSchema: agent.deepAgentConfig?.contextSchema,
107
107
  middleware: compileMiddlewareConfigs(agent.deepAgentConfig?.middleware, models, agent.id),
108
+ passthrough: typeof agent.deepAgentConfig?.passthrough === "object" && agent.deepAgentConfig.passthrough
109
+ ? { ...agent.deepAgentConfig.passthrough }
110
+ : undefined,
108
111
  };
109
112
  }
110
113
  function resolveDirectPrompt(agent) {
@@ -242,7 +245,13 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
242
245
  stateSchema: agent.langchainAgentConfig?.stateSchema,
243
246
  responseFormat: agent.langchainAgentConfig?.responseFormat,
244
247
  contextSchema: agent.langchainAgentConfig?.contextSchema,
248
+ filesystem: typeof agent.langchainAgentConfig?.filesystem === "object" && agent.langchainAgentConfig.filesystem
249
+ ? { ...agent.langchainAgentConfig.filesystem }
250
+ : undefined,
245
251
  middleware: compileMiddlewareConfigs(agent.langchainAgentConfig?.middleware, models, agent.id),
252
+ passthrough: typeof agent.langchainAgentConfig?.passthrough === "object" && agent.langchainAgentConfig.passthrough
253
+ ? { ...agent.langchainAgentConfig.passthrough }
254
+ : undefined,
246
255
  subagents: agent.subagentRefs.map((ref) => {
247
256
  const subagent = agents.get(resolveRefId(ref));
248
257
  if (!subagent) {
@@ -282,6 +291,9 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
282
291
  responseFormat: agent.deepAgentConfig?.responseFormat,
283
292
  contextSchema: agent.deepAgentConfig?.contextSchema,
284
293
  middleware: compileMiddlewareConfigs(agent.deepAgentConfig?.middleware, models, agent.id),
294
+ passthrough: typeof agent.deepAgentConfig?.passthrough === "object" && agent.deepAgentConfig.passthrough
295
+ ? { ...agent.deepAgentConfig.passthrough }
296
+ : undefined,
285
297
  description: agent.description,
286
298
  subagents: agent.subagentRefs.map((ref) => {
287
299
  const subagent = agents.get(resolveRefId(ref));
@@ -61,6 +61,11 @@ function toArray(value) {
61
61
  function asObject(value) {
62
62
  return typeof value === "object" && value ? value : undefined;
63
63
  }
64
+ function asMutableObject(value) {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value)
66
+ ? { ...value }
67
+ : undefined;
68
+ }
64
69
  function normalizeCatalogSpec(document, options = {}) {
65
70
  const typed = asObject(document);
66
71
  const spec = typed?.spec;
@@ -211,9 +216,29 @@ function readCapabilities(value) {
211
216
  return Object.keys(capabilities).length > 0 ? capabilities : undefined;
212
217
  }
213
218
  function readExecutionConfig(value) {
214
- return typeof value === "object" && value !== null && !Array.isArray(value)
215
- ? { ...value }
216
- : undefined;
219
+ return asMutableObject(value);
220
+ }
221
+ function readExecutionAgentConfig(item) {
222
+ const execution = readExecutionConfig(item.execution);
223
+ return asMutableObject(execution?.config) ?? {};
224
+ }
225
+ function readRuntimeConfig(item) {
226
+ return asMutableObject(item.runtime);
227
+ }
228
+ function cloneConfigValue(value) {
229
+ if (Array.isArray(value)) {
230
+ return value.map((item) => cloneConfigValue(item));
231
+ }
232
+ if (typeof value === "object" && value !== null) {
233
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneConfigValue(entry)]));
234
+ }
235
+ return value;
236
+ }
237
+ function readPassthroughConfig(item, consumedKeys) {
238
+ const passthrough = Object.fromEntries(Object.entries(item)
239
+ .filter(([key]) => !consumedKeys.includes(key))
240
+ .map(([key, value]) => [key, cloneConfigValue(value)]));
241
+ return Object.keys(passthrough).length > 0 ? passthrough : undefined;
217
242
  }
218
243
  function resolveExecutionBackend(item, current) {
219
244
  const execution = readExecutionConfig(item.execution) ?? readExecutionConfig(current?.execution);
@@ -230,43 +255,97 @@ function resolveExecutionBackend(item, current) {
230
255
  }
231
256
  return undefined;
232
257
  }
233
- function readSharedAgentConfig(item) {
234
- const middleware = readMiddlewareArray(item.middleware);
258
+ function readExecutionSingleRef(item, key) {
259
+ const execution = readExecutionConfig(item.execution);
260
+ return readSingleRef(execution?.[key]);
261
+ }
262
+ function readExecutionRefArray(item, key) {
263
+ const execution = readExecutionConfig(item.execution);
264
+ return readRefArray(execution?.[key]);
265
+ }
266
+ function readExecutionPathArray(item, key) {
267
+ const execution = readExecutionConfig(item.execution);
268
+ return readPathArray(execution?.[key]);
269
+ }
270
+ function readExecutionObjectArray(item, key) {
271
+ const execution = readExecutionConfig(item.execution);
272
+ return readObjectArray(execution?.[key]);
273
+ }
274
+ function readSharedAgentConfig(config) {
275
+ const middleware = readMiddlewareArray(config.middleware);
235
276
  return {
236
- ...(typeof item.systemPrompt === "string" ? { systemPrompt: item.systemPrompt } : {}),
237
- ...((typeof item.checkpointer === "object" && item.checkpointer) || typeof item.checkpointer === "boolean"
238
- ? { checkpointer: item.checkpointer }
277
+ ...(typeof config.systemPrompt === "string" ? { systemPrompt: config.systemPrompt } : {}),
278
+ ...((typeof config.checkpointer === "object" && config.checkpointer) || typeof config.checkpointer === "boolean"
279
+ ? { checkpointer: config.checkpointer }
239
280
  : {}),
240
- ...(typeof item.interruptOn === "object" && item.interruptOn ? { interruptOn: item.interruptOn } : {}),
241
- ...(item.stateSchema !== undefined ? { stateSchema: item.stateSchema } : {}),
242
- ...(item.responseFormat !== undefined ? { responseFormat: item.responseFormat } : {}),
243
- ...(item.contextSchema !== undefined ? { contextSchema: item.contextSchema } : {}),
244
- ...(item.includeAgentName === "inline" ? { includeAgentName: "inline" } : {}),
245
- ...(item.version === "v1" || item.version === "v2" ? { version: item.version } : {}),
281
+ ...(typeof config.interruptOn === "object" && config.interruptOn ? { interruptOn: config.interruptOn } : {}),
282
+ ...(config.stateSchema !== undefined ? { stateSchema: config.stateSchema } : {}),
283
+ ...(config.responseFormat !== undefined ? { responseFormat: config.responseFormat } : {}),
284
+ ...(config.contextSchema !== undefined ? { contextSchema: config.contextSchema } : {}),
285
+ ...(config.includeAgentName === "inline" ? { includeAgentName: "inline" } : {}),
286
+ ...(config.version === "v1" || config.version === "v2" ? { version: config.version } : {}),
287
+ ...(typeof config.filesystem === "object" && config.filesystem ? { filesystem: config.filesystem } : {}),
246
288
  ...(middleware ? { middleware } : {}),
247
289
  };
248
290
  }
249
291
  function readLangchainAgentConfig(item) {
292
+ const config = readExecutionAgentConfig(item);
293
+ const passthrough = readPassthroughConfig(config, [
294
+ "systemPrompt",
295
+ "checkpointer",
296
+ "interruptOn",
297
+ "stateSchema",
298
+ "responseFormat",
299
+ "contextSchema",
300
+ "includeAgentName",
301
+ "version",
302
+ "middleware",
303
+ "backend",
304
+ "store",
305
+ "taskDescription",
306
+ "generalPurposeAgent",
307
+ "filesystem",
308
+ ]);
250
309
  return {
251
- ...readSharedAgentConfig(item),
252
- ...(typeof item.store === "object" && item.store ? { store: item.store } : {}),
253
- ...(typeof item.taskDescription === "string" && item.taskDescription.trim() ? { taskDescription: item.taskDescription } : {}),
254
- ...(typeof item.generalPurposeAgent === "boolean" ? { generalPurposeAgent: item.generalPurposeAgent } : {}),
310
+ ...readSharedAgentConfig(config),
311
+ ...(typeof config.store === "object" && config.store ? { store: config.store } : {}),
312
+ ...(typeof config.taskDescription === "string" && config.taskDescription.trim() ? { taskDescription: config.taskDescription } : {}),
313
+ ...(typeof config.generalPurposeAgent === "boolean" ? { generalPurposeAgent: config.generalPurposeAgent } : {}),
314
+ ...(passthrough ? { passthrough } : {}),
255
315
  };
256
316
  }
257
317
  function readDeepAgentConfig(item) {
318
+ const config = readExecutionAgentConfig(item);
319
+ const passthrough = readPassthroughConfig(config, [
320
+ "systemPrompt",
321
+ "checkpointer",
322
+ "interruptOn",
323
+ "stateSchema",
324
+ "responseFormat",
325
+ "contextSchema",
326
+ "includeAgentName",
327
+ "version",
328
+ "middleware",
329
+ "backend",
330
+ "store",
331
+ "taskDescription",
332
+ "generalPurposeAgent",
333
+ "filesystem",
334
+ ]);
258
335
  return {
259
- ...readSharedAgentConfig(item),
260
- ...(typeof item.backend === "object" && item.backend ? { backend: item.backend } : {}),
261
- ...(typeof item.store === "object" && item.store ? { store: item.store } : {}),
262
- ...(typeof item.taskDescription === "string" && item.taskDescription.trim() ? { taskDescription: item.taskDescription } : {}),
263
- ...(typeof item.generalPurposeAgent === "boolean" ? { generalPurposeAgent: item.generalPurposeAgent } : {}),
336
+ ...readSharedAgentConfig(config),
337
+ ...(typeof config.backend === "object" && config.backend ? { backend: config.backend } : {}),
338
+ ...(typeof config.store === "object" && config.store ? { store: config.store } : {}),
339
+ ...(typeof config.taskDescription === "string" && config.taskDescription.trim() ? { taskDescription: config.taskDescription } : {}),
340
+ ...(typeof config.generalPurposeAgent === "boolean" ? { generalPurposeAgent: config.generalPurposeAgent } : {}),
341
+ ...(passthrough ? { passthrough } : {}),
264
342
  };
265
343
  }
266
344
  export function parseAgentItem(item, sourcePath) {
267
- const subagentRefs = readRefArray(item.subagents);
268
- const subagentPathRefs = readPathArray(item.subagents);
345
+ const subagentRefs = readExecutionRefArray(item, "subagents");
346
+ const subagentPathRefs = readExecutionPathArray(item, "subagents");
269
347
  const executionMode = String(resolveExecutionBackend(item) ?? "deepagent");
348
+ const runtime = readRuntimeConfig(item);
270
349
  return {
271
350
  id: String(item.id),
272
351
  executionMode: executionMode,
@@ -274,12 +353,12 @@ export function parseAgentItem(item, sourcePath) {
274
353
  ? { delegation: true, memory: true }
275
354
  : { delegation: true, memory: true }),
276
355
  description: String(item.description ?? ""),
277
- modelRef: readSingleRef(item.modelRef) ?? "",
278
- runRoot: typeof item.runRoot === "string" ? item.runRoot : undefined,
279
- toolRefs: readRefArray(item.tools),
280
- mcpServers: readObjectArray(item.mcpServers),
281
- skillPathRefs: readPathArray(item.skills),
282
- memorySources: readPathArray(item.memory),
356
+ modelRef: readExecutionSingleRef(item, "modelRef") ?? "",
357
+ runRoot: typeof runtime?.runRoot === "string" ? runtime.runRoot : undefined,
358
+ toolRefs: readExecutionRefArray(item, "tools"),
359
+ mcpServers: readExecutionObjectArray(item, "mcpServers"),
360
+ skillPathRefs: readExecutionPathArray(item, "skills"),
361
+ memorySources: readExecutionPathArray(item, "memory"),
283
362
  subagentRefs,
284
363
  subagentPathRefs,
285
364
  langchainAgentConfig: readLangchainAgentConfig(item),
@@ -38,6 +38,9 @@ function validateMiddlewareConfig(agent) {
38
38
  if (kind === "modelFallback" && !Array.isArray(typed.fallbackModels) && !Array.isArray(typed.models)) {
39
39
  throw new Error(`Agent ${agent.id} modelFallback middleware requires fallbackModels or models`);
40
40
  }
41
+ if (kind === "humanInTheLoop" && typeof typed.interruptOn !== "object") {
42
+ throw new Error(`Agent ${agent.id} humanInTheLoop middleware requires interruptOn`);
43
+ }
41
44
  if (kind === "pii" && typeof typed.piiType !== "string") {
42
45
  throw new Error(`Agent ${agent.id} pii middleware requires piiType`);
43
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
@@ -26,11 +26,6 @@
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"
34
29
  }
35
30
  },
36
31
  "dependencies": {
@@ -38,7 +33,7 @@
38
33
  "@langchain/community": "^1.1.24",
39
34
  "@langchain/core": "^1.1.33",
40
35
  "@langchain/google": "^0.1.7",
41
- "@langchain/langgraph": "^1.2.3",
36
+ "@langchain/langgraph": "^1.2.5",
42
37
  "@langchain/langgraph-checkpoint-sqlite": "^1.0.1",
43
38
  "@langchain/ollama": "^1.2.6",
44
39
  "@langchain/openai": "^1.1.0",
@@ -46,7 +41,7 @@
46
41
  "@llamaindex/ollama": "^0.1.23",
47
42
  "@modelcontextprotocol/sdk": "^1.12.0",
48
43
  "deepagents": "1.8.4",
49
- "langchain": "1.2.34",
44
+ "langchain": "^1.2.36",
50
45
  "llamaindex": "^0.12.1",
51
46
  "mustache": "^4.2.0",
52
47
  "yaml": "^2.8.1",
@@ -55,9 +50,8 @@
55
50
  "scripts": {
56
51
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
57
52
  "check": "tsc -p tsconfig.json --noEmit",
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",
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-capabilities.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/yaml-format.test.ts",
59
54
  "test:real-providers": "vitest run test/real-provider-harness.test.ts",
60
- "test:integration": "npm run build && node scripts/integration-wallee-browser.mjs",
61
55
  "release:prepare": "npm version patch --no-git-tag-version && node ./scripts/sync-example-version.mjs",
62
56
  "release:pack": "npm pack --dry-run",
63
57
  "release:publish": "npm publish --access public --registry https://registry.npmjs.org/"