@botbotgo/agent-harness 0.0.95 → 0.0.96

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.
@@ -28,28 +28,28 @@ function buildCodingRuntimeWorkflow(options) {
28
28
  const verifierAgent = readStringOption(options, "verifierAgent");
29
29
  const needsVerification = Boolean(verifierAgent) || options.runIntegrationVerification === true;
30
30
  return {
31
- entryNode: "planner",
31
+ entryNode: "step1",
32
32
  nodes: [
33
- { id: "planner", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
34
- { id: "coder", kind: "agent", agent: coderAgent },
35
- { id: "reviewer", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT },
36
- { id: "replanner", kind: "llm", role: "replanner", prompt: DEFAULT_REPLANNER_PROMPT },
33
+ { id: "step1", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
34
+ { id: "step2", kind: "agent", agent: coderAgent },
35
+ { id: "step3", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT },
36
+ { id: "step4", kind: "llm", role: "replanner", prompt: DEFAULT_REPLANNER_PROMPT },
37
37
  ...(needsVerification
38
- ? [{ id: "approval", kind: "approval" }, { id: "verifier", kind: "agent", agent: verifierAgent ?? coderAgent }]
38
+ ? [{ id: "step5", kind: "approval" }, { id: "step6", kind: "agent", agent: verifierAgent ?? coderAgent }]
39
39
  : []),
40
- { id: "finalizer", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
40
+ { id: "step7", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
41
41
  ],
42
42
  edges: [
43
- { from: "planner", to: "coder" },
44
- { from: "coder", to: "reviewer", when: "has_result" },
45
- { from: "reviewer", to: needsVerification ? "approval" : "finalizer", when: "review_ok" },
46
- { from: "reviewer", to: "replanner", when: "review_incomplete" },
47
- { from: "replanner", to: "coder", when: "has_plan" },
43
+ { from: "step1", to: "step2" },
44
+ { from: "step2", to: "step3", when: "has_result" },
45
+ { from: "step3", to: needsVerification ? "step5" : "step7", when: "review_ok" },
46
+ { from: "step3", to: "step4", when: "review_incomplete" },
47
+ { from: "step4", to: "step2", when: "has_plan" },
48
48
  ...(needsVerification
49
49
  ? [
50
- { from: "approval", to: "verifier", when: "approval_approved" },
51
- { from: "approval", to: "verifier", when: "approval_edited" },
52
- { from: "verifier", to: "finalizer", when: "has_result" },
50
+ { from: "step5", to: "step6", when: "approval_approved" },
51
+ { from: "step5", to: "step6", when: "approval_edited" },
52
+ { from: "step6", to: "step7", when: "has_result" },
53
53
  ]
54
54
  : []),
55
55
  ],
@@ -61,21 +61,21 @@ function buildAssistantWorkflow(options) {
61
61
  readStringOption(options, "defaultWritingAgent") ??
62
62
  readStringOption(options, "defaultSchedulingAgent");
63
63
  return {
64
- entryNode: "intake",
64
+ entryNode: "step1",
65
65
  nodes: [
66
- { id: "intake", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
67
- ...(worker ? [{ id: "worker", kind: "agent", agent: worker }] : [{ id: "lookup", kind: "tool", tool: "repo_inventory" }]),
68
- { id: "reviewer", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT },
69
- { id: "approval", kind: "approval" },
70
- { id: "finalizer", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
66
+ { id: "step1", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
67
+ ...(worker ? [{ id: "step2", kind: "agent", agent: worker }] : [{ id: "step2", kind: "tool", tool: "repo_inventory" }]),
68
+ { id: "step3", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT },
69
+ { id: "step4", kind: "approval" },
70
+ { id: "step5", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
71
71
  ],
72
72
  edges: [
73
- { from: "intake", to: worker ? "worker" : "lookup" },
74
- { from: worker ? "worker" : "lookup", to: "reviewer", when: "has_result" },
75
- { from: "reviewer", to: "approval", when: "review_incomplete" },
76
- { from: "reviewer", to: "finalizer", when: "review_ok" },
77
- { from: "approval", to: "finalizer", when: "approval_approved" },
78
- { from: "approval", to: "finalizer", when: "approval_edited" },
73
+ { from: "step1", to: "step2" },
74
+ { from: "step2", to: "step3", when: "has_result" },
75
+ { from: "step3", to: "step4", when: "review_incomplete" },
76
+ { from: "step3", to: "step5", when: "review_ok" },
77
+ { from: "step4", to: "step5", when: "approval_approved" },
78
+ { from: "step4", to: "step5", when: "approval_edited" },
79
79
  ],
80
80
  };
81
81
  }
@@ -84,22 +84,22 @@ function buildResearchWorkflow(options) {
84
84
  const analystAgent = requireAgentOption("research-runtime", options, "analystAgent");
85
85
  const synthesizerAgent = readStringOption(options, "synthesizerAgent");
86
86
  return {
87
- entryNode: "planner",
87
+ entryNode: "step1",
88
88
  nodes: [
89
- { id: "planner", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
90
- { id: "gatherer", kind: "agent", agent: gathererAgent },
91
- { id: "coverage-review", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT },
92
- { id: "analyst", kind: "agent", agent: analystAgent },
93
- ...(synthesizerAgent ? [{ id: "synthesizer", kind: "agent", agent: synthesizerAgent }] : []),
94
- { id: "finalizer", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
89
+ { id: "step1", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
90
+ { id: "step2", kind: "agent", agent: gathererAgent },
91
+ { id: "step3", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT },
92
+ { id: "step4", kind: "agent", agent: analystAgent },
93
+ ...(synthesizerAgent ? [{ id: "step5", kind: "agent", agent: synthesizerAgent }] : []),
94
+ { id: "step6", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
95
95
  ],
96
96
  edges: [
97
- { from: "planner", to: "gatherer" },
98
- { from: "gatherer", to: "coverage-review", when: "has_result" },
99
- { from: "coverage-review", to: "analyst", when: "review_ok" },
100
- { from: "coverage-review", to: "gatherer", when: "review_incomplete" },
101
- { from: "analyst", to: synthesizerAgent ? "synthesizer" : "finalizer", when: "has_result" },
102
- ...(synthesizerAgent ? [{ from: "synthesizer", to: "finalizer", when: "has_result" }] : []),
97
+ { from: "step1", to: "step2" },
98
+ { from: "step2", to: "step3", when: "has_result" },
99
+ { from: "step3", to: "step4", when: "review_ok" },
100
+ { from: "step3", to: "step2", when: "review_incomplete" },
101
+ { from: "step4", to: synthesizerAgent ? "step5" : "step6", when: "has_result" },
102
+ ...(synthesizerAgent ? [{ from: "step5", to: "step6", when: "has_result" }] : []),
103
103
  ],
104
104
  };
105
105
  }
@@ -107,20 +107,20 @@ function buildApprovalReviewWorkflow(options) {
107
107
  const preparerAgent = requireAgentOption("approval-review-runtime", options, "preparerAgent");
108
108
  const reviewerAgent = readStringOption(options, "reviewerAgent");
109
109
  return {
110
- entryNode: "scope",
110
+ entryNode: "step1",
111
111
  nodes: [
112
- { id: "scope", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
113
- { id: "preparer", kind: "agent", agent: preparerAgent },
114
- ...(reviewerAgent ? [{ id: "reviewer-agent", kind: "agent", agent: reviewerAgent }] : [{ id: "risk-review", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT }]),
115
- { id: "approval", kind: "approval" },
116
- { id: "finalizer", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
112
+ { id: "step1", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
113
+ { id: "step2", kind: "agent", agent: preparerAgent },
114
+ ...(reviewerAgent ? [{ id: "step3", kind: "agent", agent: reviewerAgent }] : [{ id: "step3", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT }]),
115
+ { id: "step4", kind: "approval" },
116
+ { id: "step5", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
117
117
  ],
118
118
  edges: [
119
- { from: "scope", to: "preparer" },
120
- { from: "preparer", to: reviewerAgent ? "reviewer-agent" : "risk-review", when: "has_result" },
121
- { from: reviewerAgent ? "reviewer-agent" : "risk-review", to: "approval", when: "has_result" },
122
- { from: "approval", to: "finalizer", when: "approval_approved" },
123
- { from: "approval", to: "finalizer", when: "approval_edited" },
119
+ { from: "step1", to: "step2" },
120
+ { from: "step2", to: "step3", when: "has_result" },
121
+ { from: "step3", to: "step4", when: "has_result" },
122
+ { from: "step4", to: "step5", when: "approval_approved" },
123
+ { from: "step4", to: "step5", when: "approval_edited" },
124
124
  ],
125
125
  };
126
126
  }
@@ -133,30 +133,30 @@ function buildChatOperatorWorkflow(options) {
133
133
  throw new Error("LangGraph profile chat-operator requires at least one target agent in with.assistantAgent, with.researchAgent, with.codingAgent, or with.approvalAgent");
134
134
  }
135
135
  return {
136
- entryNode: "router",
136
+ entryNode: "step1",
137
137
  nodes: [
138
- { id: "router", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
139
- { id: "worker", kind: "agent", agent: routedAgent },
140
- { id: "finalizer", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
138
+ { id: "step1", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
139
+ { id: "step2", kind: "agent", agent: routedAgent },
140
+ { id: "step3", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
141
141
  ],
142
142
  edges: [
143
- { from: "router", to: "worker" },
144
- { from: "worker", to: "finalizer", when: "has_result" },
143
+ { from: "step1", to: "step2" },
144
+ { from: "step2", to: "step3", when: "has_result" },
145
145
  ],
146
146
  };
147
147
  }
148
148
  function buildCopilotSidecarWorkflow(options) {
149
149
  const coderAgent = requireAgentOption("copilot-sidecar", options, "coderAgent");
150
150
  return {
151
- entryNode: "intake",
151
+ entryNode: "step1",
152
152
  nodes: [
153
- { id: "intake", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
154
- { id: "coder", kind: "agent", agent: coderAgent },
155
- { id: "finalizer", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
153
+ { id: "step1", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
154
+ { id: "step2", kind: "agent", agent: coderAgent },
155
+ { id: "step3", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
156
156
  ],
157
157
  edges: [
158
- { from: "intake", to: "coder" },
159
- { from: "coder", to: "finalizer", when: "has_result" },
158
+ { from: "step1", to: "step2" },
159
+ { from: "step2", to: "step3", when: "has_result" },
160
160
  ],
161
161
  };
162
162
  }
@@ -166,17 +166,17 @@ function buildDelegationHubWorkflow(options) {
166
166
  throw new Error("LangGraph profile task-delegation-hub requires with.defaultWorkerFallback");
167
167
  }
168
168
  return {
169
- entryNode: "router",
169
+ entryNode: "step1",
170
170
  nodes: [
171
- { id: "router", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
172
- { id: "worker", kind: "agent", agent: workerAgent },
173
- { id: "reviewer", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT },
174
- { id: "finalizer", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
171
+ { id: "step1", kind: "llm", role: "planner", prompt: DEFAULT_PLANNER_PROMPT },
172
+ { id: "step2", kind: "agent", agent: workerAgent },
173
+ { id: "step3", kind: "llm", role: "reviewer", prompt: DEFAULT_REVIEWER_PROMPT },
174
+ { id: "step4", kind: "llm", role: "finalizer", prompt: DEFAULT_FINALIZER_PROMPT },
175
175
  ],
176
176
  edges: [
177
- { from: "router", to: "worker" },
178
- { from: "worker", to: "reviewer", when: "has_result" },
179
- { from: "reviewer", to: "finalizer", when: "review_ok" },
177
+ { from: "step1", to: "step2" },
178
+ { from: "step2", to: "step3", when: "has_result" },
179
+ { from: "step3", to: "step4", when: "review_ok" },
180
180
  ],
181
181
  };
182
182
  }
@@ -1,4 +1,8 @@
1
+ import type { CompatibleStreamPart, UpstreamRuntimeEvent } from "../../contracts/types.js";
1
2
  export type RuntimeStreamChunk = {
3
+ kind: "upstream-event";
4
+ event: UpstreamRuntimeEvent;
5
+ } | {
2
6
  kind: "content";
3
7
  content: string;
4
8
  } | {
@@ -16,6 +20,8 @@ export type RuntimeStreamChunk = {
16
20
  output: unknown;
17
21
  isError?: boolean;
18
22
  };
23
+ export declare function normalizeUpstreamRuntimeEvent(event: unknown): UpstreamRuntimeEvent;
24
+ export declare function extractCustomCompatibleStreamPart(event: unknown): CompatibleStreamPart | null;
19
25
  export declare function extractTerminalStreamOutput(event: unknown): string;
20
26
  export declare function extractReasoningStreamOutput(event: unknown): string;
21
27
  export declare function extractVisibleStreamOutput(event: unknown): string;
@@ -1,4 +1,235 @@
1
1
  import { extractReasoningText, extractVisibleOutput, hasToolCalls, readTextContent } from "./output-parsing.js";
2
+ function asObject(value) {
3
+ return typeof value === "object" && value !== null ? value : undefined;
4
+ }
5
+ function extractCustomStreamData(event) {
6
+ const typed = asObject(event);
7
+ if (!typed) {
8
+ return null;
9
+ }
10
+ const eventName = typeof typed.event === "string" ? typed.event : "";
11
+ const ns = Array.isArray(typed.ns) ? typed.ns.filter((item) => typeof item === "string") : undefined;
12
+ const data = asObject(typed.data);
13
+ if (eventName === "on_custom_event") {
14
+ return {
15
+ ...(ns && ns.length > 0 ? { ns } : {}),
16
+ data: typed.data,
17
+ };
18
+ }
19
+ if (eventName === "on_chain_stream" && data && "custom" in data) {
20
+ return {
21
+ ...(ns && ns.length > 0 ? { ns } : {}),
22
+ data: data.custom,
23
+ };
24
+ }
25
+ return null;
26
+ }
27
+ export function normalizeUpstreamRuntimeEvent(event) {
28
+ const typed = asObject(event);
29
+ const tags = Array.isArray(typed?.tags) ? typed.tags.filter((item) => typeof item === "string") : undefined;
30
+ const ns = Array.isArray(typed?.ns) ? typed.ns.filter((item) => typeof item === "string") : undefined;
31
+ const name = typeof typed?.name === "string" ? typed.name : undefined;
32
+ const eventName = typeof typed?.event === "string" ? typed.event : undefined;
33
+ const runType = typeof typed?.run_type === "string" ? typed.run_type : undefined;
34
+ const data = asObject(typed?.data);
35
+ const metadata = asObject(typed?.metadata);
36
+ const normalized = normalizeUpstreamEventShape({
37
+ raw: event,
38
+ event: eventName,
39
+ name,
40
+ runType,
41
+ metadata,
42
+ tags,
43
+ data,
44
+ ns,
45
+ });
46
+ const streamPart = normalizeCompatibleStreamPart({
47
+ raw: event,
48
+ event: eventName,
49
+ name,
50
+ runType,
51
+ metadata,
52
+ tags,
53
+ data,
54
+ ns,
55
+ normalized,
56
+ });
57
+ return {
58
+ format: "langgraph-v2",
59
+ normalized,
60
+ raw: event,
61
+ event: eventName,
62
+ name,
63
+ runType,
64
+ data,
65
+ metadata,
66
+ ...(tags && tags.length > 0 ? { tags } : {}),
67
+ ...(ns && ns.length > 0 ? { ns } : {}),
68
+ streamPart,
69
+ };
70
+ }
71
+ function normalizeUpstreamEventShape(event) {
72
+ const nodeName = event.name;
73
+ const ns = event.ns;
74
+ const interruptPayload = extractInterruptPayload(event.raw);
75
+ if (interruptPayload) {
76
+ return {
77
+ kind: "interrupt",
78
+ interrupt: parseMaybeJson(interruptPayload),
79
+ ...(ns && ns.length > 0 ? { ns } : {}),
80
+ ...(nodeName ? { nodeName } : {}),
81
+ };
82
+ }
83
+ const reasoning = extractReasoningStreamOutput(event.raw);
84
+ if (reasoning) {
85
+ return {
86
+ kind: "reasoning-delta",
87
+ text: reasoning,
88
+ ...(ns && ns.length > 0 ? { ns } : {}),
89
+ ...(nodeName ? { nodeName } : {}),
90
+ };
91
+ }
92
+ const visibleText = extractVisibleStreamOutput(event.raw);
93
+ if (visibleText) {
94
+ return {
95
+ kind: "text-delta",
96
+ source: "model",
97
+ text: visibleText,
98
+ ...(ns && ns.length > 0 ? { ns } : {}),
99
+ ...(nodeName ? { nodeName } : {}),
100
+ };
101
+ }
102
+ const stateText = extractStateStreamOutput(event.raw);
103
+ if (stateText) {
104
+ return {
105
+ kind: "text-delta",
106
+ source: "state",
107
+ text: stateText,
108
+ ...(ns && ns.length > 0 ? { ns } : {}),
109
+ ...(nodeName ? { nodeName } : {}),
110
+ };
111
+ }
112
+ if (event.event === "on_tool_start" || (event.event === "on_chain_start" && event.runType === "tool")) {
113
+ return {
114
+ kind: "tool-start",
115
+ toolName: nodeName ?? "tool",
116
+ ...(event.data && "input" in event.data ? { input: event.data.input } : {}),
117
+ ...(ns && ns.length > 0 ? { ns } : {}),
118
+ ...(nodeName ? { nodeName } : {}),
119
+ };
120
+ }
121
+ const toolResult = extractToolResult(event.raw);
122
+ if (toolResult) {
123
+ return {
124
+ kind: "tool-end",
125
+ toolName: toolResult.toolName,
126
+ ...(toolResult.output !== undefined ? { output: toolResult.output } : {}),
127
+ ...(toolResult.isError !== undefined ? { isError: toolResult.isError } : {}),
128
+ ...(ns && ns.length > 0 ? { ns } : {}),
129
+ ...(nodeName ? { nodeName } : {}),
130
+ };
131
+ }
132
+ const agentStep = extractAgentStep(event.raw);
133
+ if (agentStep) {
134
+ return {
135
+ kind: "agent-step",
136
+ label: agentStep,
137
+ ...(ns && ns.length > 0 ? { ns } : {}),
138
+ ...(nodeName ? { nodeName } : {}),
139
+ };
140
+ }
141
+ return {
142
+ kind: "run-event",
143
+ eventName: event.event ?? "unknown",
144
+ ...(event.data ? { data: event.data } : {}),
145
+ ...(ns && ns.length > 0 ? { ns } : {}),
146
+ ...(nodeName ? { nodeName } : {}),
147
+ };
148
+ }
149
+ function normalizeCompatibleStreamPart(event) {
150
+ const custom = extractCustomCompatibleStreamPart(event.raw);
151
+ if (custom) {
152
+ return custom;
153
+ }
154
+ const ns = event.ns ?? [];
155
+ const messageChunk = extractMessageCompatibleStreamPart(event);
156
+ if (messageChunk) {
157
+ return {
158
+ type: "messages",
159
+ ns,
160
+ data: [messageChunk, buildStreamPartMetadata(event)],
161
+ };
162
+ }
163
+ return {
164
+ type: "updates",
165
+ ns,
166
+ data: buildUpdateCompatiblePayload(event),
167
+ };
168
+ }
169
+ function buildStreamPartMetadata(event) {
170
+ return {
171
+ ...(event.metadata ?? {}),
172
+ ...(event.event ? { event: event.event } : {}),
173
+ ...(event.name ? { name: event.name } : {}),
174
+ ...(event.runType ? { run_type: event.runType } : {}),
175
+ ...(event.tags && event.tags.length > 0 ? { tags: event.tags } : {}),
176
+ };
177
+ }
178
+ function extractMessageCompatibleStreamPart(event) {
179
+ const typed = asObject(event.raw);
180
+ const data = asObject(typed?.data);
181
+ if (event.event === "on_chat_model_stream" && data && "chunk" in data) {
182
+ return data.chunk;
183
+ }
184
+ if (event.normalized.kind === "tool-end") {
185
+ return {
186
+ type: "tool",
187
+ name: event.normalized.toolName,
188
+ content: event.normalized.output,
189
+ ...(event.normalized.isError !== undefined ? { isError: event.normalized.isError } : {}),
190
+ };
191
+ }
192
+ return null;
193
+ }
194
+ function buildUpdateCompatiblePayload(event) {
195
+ const key = event.name ?? event.event ?? "event";
196
+ return {
197
+ [key]: extractUpdateNodePayload(event),
198
+ };
199
+ }
200
+ function extractUpdateNodePayload(event) {
201
+ const data = event.data;
202
+ if (event.event === "on_chain_stream" && data && "chunk" in data) {
203
+ return data.chunk;
204
+ }
205
+ if ((event.event === "on_tool_start" || event.event === "on_chain_start") && data && "input" in data) {
206
+ return { input: data.input };
207
+ }
208
+ if ((event.event === "on_tool_end" || event.event === "on_chain_end" || event.event === "on_chat_model_end") && data && "output" in data) {
209
+ return data.output;
210
+ }
211
+ if ((event.event === "on_tool_error" || event.event === "on_chain_error") && data) {
212
+ return {
213
+ ...(data.output !== undefined ? { output: data.output } : {}),
214
+ ...(data.error !== undefined ? { error: data.error } : {}),
215
+ };
216
+ }
217
+ if (event.normalized.kind === "interrupt") {
218
+ return { __interrupt__: event.normalized.interrupt };
219
+ }
220
+ return data ?? {};
221
+ }
222
+ export function extractCustomCompatibleStreamPart(event) {
223
+ const custom = extractCustomStreamData(event);
224
+ if (!custom) {
225
+ return null;
226
+ }
227
+ return {
228
+ type: "custom",
229
+ ns: custom.ns ?? [],
230
+ data: custom.data,
231
+ };
232
+ }
2
233
  function formatStepValue(value, maxLength = 120) {
3
234
  if (value == null)
4
235
  return "";
@@ -133,14 +133,25 @@ export function inferRoutingBindings(workspace) {
133
133
  const runtimeEntryBindings = Array.from(workspace.bindings.values()).filter((binding) => isRuntimeEntryBinding(binding));
134
134
  const orchestrationHosts = runtimeEntryBindings.filter((binding) => binding.agent.executionMode === "deepagent" || binding.agent.executionMode === "langgraph" || Boolean(binding.deepAgentParams));
135
135
  const routingHosts = orchestrationHosts.length > 0 ? orchestrationHosts : runtimeEntryBindings;
136
+ const deepAgentHosts = routingHosts.filter((binding) => binding.agent.executionMode === "deepagent" || Boolean(binding.deepAgentParams));
137
+ const nonDeepAgentHosts = routingHosts.filter((binding) => !deepAgentHosts.includes(binding));
136
138
  const researchBinding = routingHosts.find((binding) => binding.agent.id === "research-lite" || binding.agent.id === "research");
137
139
  const directBinding = routingHosts.find((binding) => binding.agent.id === "direct");
138
140
  const delegationHosts = routingHosts.filter((binding) => isDelegationCapableBinding(binding));
141
+ const deepAgentDelegationHosts = deepAgentHosts.filter((binding) => isDelegationCapableBinding(binding));
142
+ const nonDeepAgentDelegationHosts = nonDeepAgentHosts.filter((binding) => isDelegationCapableBinding(binding));
139
143
  const lightweightHosts = routingHosts.filter((binding) => !isDelegationCapableBinding(binding));
140
144
  const defaultOrchestratingHost = routingHosts.find((binding) => binding.agent.id === "orchestra") ??
141
- delegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
142
- delegationHosts[0];
143
- const delegationPreferredSecondary = delegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
145
+ deepAgentDelegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
146
+ deepAgentDelegationHosts[0] ??
147
+ deepAgentHosts[0] ??
148
+ nonDeepAgentDelegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
149
+ nonDeepAgentDelegationHosts[0] ??
150
+ nonDeepAgentHosts[0];
151
+ const delegationPreferredSecondary = deepAgentDelegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
152
+ deepAgentDelegationHosts[0] ??
153
+ nonDeepAgentDelegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
154
+ nonDeepAgentDelegationHosts[0] ??
144
155
  delegationHosts[0];
145
156
  const genericLightweightHost = lightweightHosts.find((binding) => binding.agent.id !== researchBinding?.agent.id);
146
157
  const primaryBinding = defaultOrchestratingHost ?? directBinding ?? genericLightweightHost ?? routingHosts[0] ?? runtimeEntryBindings[0];
@@ -261,6 +261,22 @@ function resolveLangGraphWorkflowConfig(agent, refs) {
261
261
  agent.langchainAgentConfig.passthrough.with
262
262
  ? { ...agent.langchainAgentConfig.passthrough.with }
263
263
  : undefined;
264
+ const workspaceProfile = profile ? getWorkspaceObject(refs, `runtime-profile/${profile}`) : undefined;
265
+ if (workspaceProfile && workspaceProfile.kind !== "runtime-profile") {
266
+ throw new Error(`Agent ${agent.id} profile ${profile} does not resolve to a RuntimeProfile object`);
267
+ }
268
+ const workspaceProfileValue = workspaceProfile?.value && typeof workspaceProfile.value === "object"
269
+ ? workspaceProfile.value
270
+ : undefined;
271
+ const workspaceProfileDefaults = typeof workspaceProfileValue?.defaults === "object" && workspaceProfileValue.defaults
272
+ ? workspaceProfileValue.defaults
273
+ : undefined;
274
+ const mergedProfileWith = workspaceProfileDefaults || profileWith
275
+ ? {
276
+ ...(workspaceProfileDefaults ?? {}),
277
+ ...(profileWith ?? {}),
278
+ }
279
+ : undefined;
264
280
  const preset = typeof agent.langchainAgentConfig?.preset === "string" && agent.langchainAgentConfig.preset.trim()
265
281
  ? agent.langchainAgentConfig.preset.trim()
266
282
  : typeof agent.langchainAgentConfig?.passthrough === "object" &&
@@ -284,21 +300,41 @@ function resolveLangGraphWorkflowConfig(agent, refs) {
284
300
  agent.langchainAgentConfig.passthrough.langgraph
285
301
  ? agent.langchainAgentConfig.passthrough.langgraph
286
302
  : undefined;
303
+ const profileWorkflowRef = typeof workspaceProfileValue?.workflowRef === "string" && workspaceProfileValue.workflowRef.trim()
304
+ ? workspaceProfileValue.workflowRef.trim()
305
+ : typeof workspaceProfileValue?.workflow === "string" && workspaceProfileValue.workflow.trim()
306
+ ? workspaceProfileValue.workflow.trim()
307
+ : typeof workspaceProfileValue?.workflow === "object" &&
308
+ workspaceProfileValue.workflow &&
309
+ typeof workspaceProfileValue.workflow.ref === "string" &&
310
+ workspaceProfileValue.workflow.ref.trim()
311
+ ? workspaceProfileValue.workflow.ref.trim()
312
+ : undefined;
313
+ const resolvedProfileWorkflowConfig = !workflowConfig && profileWorkflowRef
314
+ ? materializeWorkspaceObjectConfig(refs, profileWorkflowRef, ["langgraph-workflow"], `Agent ${agent.id} profile ${profile} workflow`)
315
+ : undefined;
287
316
  if (!workflowConfig) {
288
- return profile || preset ? { ...(profile ? { profile } : {}), ...(profileWith ? { with: profileWith } : {}), ...(preset ? { preset } : {}) } : undefined;
317
+ return profile || preset || resolvedProfileWorkflowConfig
318
+ ? {
319
+ ...(profile ? { profile } : {}),
320
+ ...(mergedProfileWith ? { with: mergedProfileWith } : {}),
321
+ ...(preset ? { preset } : {}),
322
+ ...(resolvedProfileWorkflowConfig ? { config: resolvedProfileWorkflowConfig } : {}),
323
+ }
324
+ : undefined;
289
325
  }
290
326
  if (isRefConfig(workflowConfig)) {
291
327
  return {
292
328
  config: materializeWorkspaceObjectConfig(refs, workflowConfig.ref, ["langgraph-workflow"], `Agent ${agent.id} workflow`),
293
329
  ...(profile ? { profile } : {}),
294
- ...(profileWith ? { with: profileWith } : {}),
330
+ ...(mergedProfileWith ? { with: mergedProfileWith } : {}),
295
331
  ...(preset ? { preset } : {}),
296
332
  };
297
333
  }
298
334
  return {
299
335
  config: workflowConfig,
300
336
  ...(profile ? { profile } : {}),
301
- ...(profileWith ? { with: profileWith } : {}),
337
+ ...(mergedProfileWith ? { with: mergedProfileWith } : {}),
302
338
  ...(preset ? { preset } : {}),
303
339
  };
304
340
  }
@@ -152,6 +152,8 @@ function normalizeKind(kind) {
152
152
  return "runtime-memory";
153
153
  case "LangGraphWorkflow":
154
154
  return "langgraph-workflow";
155
+ case "RuntimeProfile":
156
+ return "runtime-profile";
155
157
  case "Prompt":
156
158
  return "prompt";
157
159
  case "McpServer":
@@ -473,7 +475,9 @@ async function objectItemsFromDocument(document, sourcePath) {
473
475
  ? normalizeCatalogSpec(document, { defaultKind: "McpServer" })
474
476
  : catalogKind === "LangGraphWorkflows"
475
477
  ? normalizeCatalogSpec(document, { defaultKind: "LangGraphWorkflow" })
476
- : [];
478
+ : catalogKind === "RuntimeProfiles"
479
+ ? normalizeCatalogSpec(document, { defaultKind: "RuntimeProfile" })
480
+ : [];
477
481
  if (catalogItems.length > 0) {
478
482
  return catalogItems;
479
483
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.95",
3
+ "version": "0.0.96",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",