@botbotgo/agent-harness 0.0.22 → 0.0.24
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 +60 -3
- package/dist/config/models.yaml +29 -0
- package/dist/mcp.js +2 -1
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +1 -0
- package/dist/resource/resource-impl.js +2 -1
- package/dist/runtime/harness.d.ts +9 -0
- package/dist/runtime/harness.js +124 -176
- package/dist/workspace/compile.js +3 -154
- package/dist/workspace/object-loader.js +113 -204
- package/dist/workspace/tool-hydration.d.ts +3 -0
- package/dist/workspace/tool-hydration.js +158 -0
- package/package.json +1 -1
- package/dist/config/model.yaml +0 -44
- /package/dist/config/{direct.yaml → agents/direct.yaml} +0 -0
- /package/dist/config/{orchestra.yaml → agents/orchestra.yaml} +0 -0
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ It helps developers:
|
|
|
9
9
|
- define agents in YAML
|
|
10
10
|
- keep workspace behavior in `config/workspace.yaml`
|
|
11
11
|
- keep shared bootstrap context in `config/agent-context.md`
|
|
12
|
-
- discover tools and
|
|
12
|
+
- discover tools and SKILL packages from `resources/`
|
|
13
|
+
- bridge remote MCP servers into agent toolsets
|
|
14
|
+
- expose harness-managed tools as an MCP server
|
|
13
15
|
- run through either LangChain v1 or DeepAgents with one app-facing API
|
|
14
16
|
|
|
15
17
|
Why it works:
|
|
@@ -54,6 +56,8 @@ your-workspace/
|
|
|
54
56
|
skills/
|
|
55
57
|
```
|
|
56
58
|
|
|
59
|
+
Use the standard layout only. Agent entry files must live under `config/agents/`; root-level files such as `agent.yaml`, `orchestra.yaml`, and `direct.yaml` are not loaded.
|
|
60
|
+
|
|
57
61
|
Minimal usage:
|
|
58
62
|
|
|
59
63
|
```ts
|
|
@@ -78,7 +82,9 @@ try {
|
|
|
78
82
|
- One workspace for agents, models, tools, and runtime behavior
|
|
79
83
|
- One API for both LangChain v1 and DeepAgents
|
|
80
84
|
- Built-in routing for direct and orchestration flows
|
|
81
|
-
- Auto-discovered local tools and
|
|
85
|
+
- Auto-discovered local tools and SKILL packages from `resources/tools/` and `resources/skills/`
|
|
86
|
+
- MCP bridge support: agent YAML can mount remote or local MCP servers as agent tools
|
|
87
|
+
- MCP server support: expose harness-managed tools over stdio or an in-memory MCP server
|
|
82
88
|
- Built-in thread state, approvals, resumable runs, and long-term memory
|
|
83
89
|
- MCP server helpers plus background checkpoint maintenance
|
|
84
90
|
|
|
@@ -146,6 +152,51 @@ const result = await run(harness, {
|
|
|
146
152
|
});
|
|
147
153
|
```
|
|
148
154
|
|
|
155
|
+
### Use Skills And Local Tools
|
|
156
|
+
|
|
157
|
+
`agent-harness` treats `resources/skills/` as SKILL packages and `resources/tools/` as executable local tools.
|
|
158
|
+
|
|
159
|
+
Point agent YAML at both:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
spec:
|
|
163
|
+
skills:
|
|
164
|
+
- path: resources/skills/reviewer
|
|
165
|
+
tools:
|
|
166
|
+
- ref: tool/local-toolset
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Bridge MCP Servers Into Agents
|
|
170
|
+
|
|
171
|
+
Use `mcpServers:` inside agent YAML to bridge MCP servers into the agent's tool list:
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
spec:
|
|
175
|
+
mcpServers:
|
|
176
|
+
- name: browser
|
|
177
|
+
command: node
|
|
178
|
+
args: ["./mcp-browser-server.mjs"]
|
|
179
|
+
- name: docs
|
|
180
|
+
transport: http
|
|
181
|
+
url: https://example.com/mcp
|
|
182
|
+
token: ${DOCS_MCP_TOKEN}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The harness discovers MCP tools, filters them through the agent config, and exposes them to the runtime like any other tool.
|
|
186
|
+
|
|
187
|
+
### Expose Harness Tools As An MCP Server
|
|
188
|
+
|
|
189
|
+
Use the MCP server helpers when another client should connect to the harness as an MCP server:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { createToolMcpServer, serveToolsOverStdio } from "@botbotgo/agent-harness";
|
|
193
|
+
|
|
194
|
+
const server = await createToolMcpServer(harness, { agentId: "orchestra" });
|
|
195
|
+
await serveToolsOverStdio(harness, { agentId: "orchestra" });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
If you omit `serverInfo`, the harness uses `agent-harness-<agentId>` as the MCP server name and the current package version as the server version.
|
|
199
|
+
|
|
149
200
|
### Read Back Thread State
|
|
150
201
|
|
|
151
202
|
```ts
|
|
@@ -221,6 +272,12 @@ Use `config/agents/*.yaml` to configure agents. Common fields include:
|
|
|
221
272
|
Use `resources/` for executable extensions:
|
|
222
273
|
|
|
223
274
|
- `resources/tools/` for local tool modules
|
|
224
|
-
- `resources/skills/` for
|
|
275
|
+
- `resources/skills/` for SKILL packages
|
|
225
276
|
|
|
226
277
|
Each resource package should include its own `package.json`.
|
|
278
|
+
|
|
279
|
+
### Skills And MCP
|
|
280
|
+
|
|
281
|
+
- Use `resources/skills/` for SKILL packages that carry reusable instructions, templates, scripts, and metadata
|
|
282
|
+
- Use `mcpServers:` in `config/agents/*.yaml` when you want the harness to bridge external MCP tools into an agent
|
|
283
|
+
- Use `createToolMcpServer(...)` or `serveToolsOverStdio(...)` when you want the harness itself to act as an MCP server for another client
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# agent-harness feature: schema version for this declarative config object.
|
|
2
|
+
apiVersion: agent-harness/v1alpha1
|
|
3
|
+
# agent-harness feature: object type for named model presets.
|
|
4
|
+
kind: Models
|
|
5
|
+
spec:
|
|
6
|
+
- name: default
|
|
7
|
+
# ====================
|
|
8
|
+
# LangChain v1 Features
|
|
9
|
+
# ====================
|
|
10
|
+
# LangChain aligned feature: provider family or integration namespace.
|
|
11
|
+
# Common options in this harness today include:
|
|
12
|
+
# - `ollama`
|
|
13
|
+
# - `openai`
|
|
14
|
+
# - `openai-compatible`
|
|
15
|
+
# - `anthropic`
|
|
16
|
+
# - `google` / `google-genai` / `gemini`
|
|
17
|
+
# The runtime adapter uses this to select the concrete LangChain chat model implementation.
|
|
18
|
+
provider: ollama
|
|
19
|
+
# LangChain aligned feature: concrete model identifier passed to the selected provider integration.
|
|
20
|
+
# Example values depend on `provider`, such as `gpt-oss:latest` for `ollama`.
|
|
21
|
+
model: gpt-oss:latest
|
|
22
|
+
init:
|
|
23
|
+
# LangChain aligned feature: provider-specific initialization options.
|
|
24
|
+
# Available keys are provider-specific; common examples include `baseUrl`, `temperature`, and auth/client settings.
|
|
25
|
+
# `baseUrl` configures the Ollama-compatible endpoint used by the model client.
|
|
26
|
+
# For `openai-compatible`, `baseUrl` is normalized into the ChatOpenAI `configuration.baseURL` field.
|
|
27
|
+
baseUrl: https://ollama-rtx-4070.easynet.world/
|
|
28
|
+
# LangChain aligned feature: provider/model initialization option controlling sampling temperature.
|
|
29
|
+
temperature: 0.2
|
package/dist/mcp.js
CHANGED
|
@@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { AGENT_HARNESS_VERSION } from "./package-version.js";
|
|
5
6
|
import { loadToolModuleDefinition } from "./tool-modules.js";
|
|
6
7
|
function asResolvedTool(value) {
|
|
7
8
|
return typeof value === "object" && value !== null ? value : null;
|
|
@@ -86,7 +87,7 @@ function jsonSchemaToZod(schema) {
|
|
|
86
87
|
export async function createToolMcpServerFromHarness(harness, options) {
|
|
87
88
|
const server = new McpServer({
|
|
88
89
|
name: options.serverInfo?.name ?? `agent-harness-${options.agentId}`,
|
|
89
|
-
version: options.serverInfo?.version ??
|
|
90
|
+
version: options.serverInfo?.version ?? AGENT_HARNESS_VERSION,
|
|
90
91
|
});
|
|
91
92
|
const allowedNames = options.includeToolNames ? new Set(options.includeToolNames) : null;
|
|
92
93
|
for (const { compiledTool, resolvedTool } of harness.resolveAgentTools(options.agentId)) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.23";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.23";
|
|
@@ -9,6 +9,7 @@ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
|
9
9
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
10
10
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
11
11
|
import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
|
|
12
|
+
import { AGENT_HARNESS_VERSION } from "../package-version.js";
|
|
12
13
|
import { isSupportedToolModulePath, loadToolModuleDefinition } from "../tool-modules.js";
|
|
13
14
|
import { resolveIsolatedResourceModulePath } from "./isolation.js";
|
|
14
15
|
import { ensureExternalResourceSource, ensureExternalSource, isExternalSourceLocator, parseExternalSourceLocator } from "./sources.js";
|
|
@@ -176,7 +177,7 @@ export async function getOrCreateMcpClient(config) {
|
|
|
176
177
|
const loading = (async () => {
|
|
177
178
|
const client = new Client({
|
|
178
179
|
name: "agent-harness",
|
|
179
|
-
version:
|
|
180
|
+
version: AGENT_HARNESS_VERSION,
|
|
180
181
|
});
|
|
181
182
|
const headers = {
|
|
182
183
|
...(config.headers ?? {}),
|
|
@@ -44,6 +44,15 @@ export declare class AgentHarness {
|
|
|
44
44
|
threadId?: string;
|
|
45
45
|
}): Promise<string>;
|
|
46
46
|
private emit;
|
|
47
|
+
private ensureThreadStarted;
|
|
48
|
+
private loadPriorHistory;
|
|
49
|
+
private appendAssistantMessage;
|
|
50
|
+
private invokeWithHistory;
|
|
51
|
+
private emitOutputDeltaAndCreateItem;
|
|
52
|
+
private emitRunCreated;
|
|
53
|
+
private setRunStateAndEmit;
|
|
54
|
+
private requestApprovalAndEmit;
|
|
55
|
+
private emitSyntheticFallback;
|
|
47
56
|
private persistApproval;
|
|
48
57
|
private resolveApprovalRecord;
|
|
49
58
|
private isDecisionRun;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -269,6 +269,95 @@ export class AgentHarness {
|
|
|
269
269
|
this.eventBus.publish(event);
|
|
270
270
|
return event;
|
|
271
271
|
}
|
|
272
|
+
async ensureThreadStarted(selectedAgentId, binding, input, existingThreadId) {
|
|
273
|
+
const threadId = existingThreadId ?? createPersistentId();
|
|
274
|
+
const runId = createPersistentId();
|
|
275
|
+
const createdAt = new Date().toISOString();
|
|
276
|
+
if (!existingThreadId) {
|
|
277
|
+
await this.persistence.createThread({
|
|
278
|
+
threadId,
|
|
279
|
+
agentId: selectedAgentId,
|
|
280
|
+
runId,
|
|
281
|
+
status: "running",
|
|
282
|
+
createdAt,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
await this.persistence.appendThreadMessage(threadId, {
|
|
286
|
+
role: "user",
|
|
287
|
+
content: input,
|
|
288
|
+
runId,
|
|
289
|
+
createdAt,
|
|
290
|
+
});
|
|
291
|
+
await this.persistence.createRun({
|
|
292
|
+
threadId,
|
|
293
|
+
runId,
|
|
294
|
+
agentId: binding.agent.id,
|
|
295
|
+
executionMode: binding.agent.executionMode,
|
|
296
|
+
createdAt,
|
|
297
|
+
});
|
|
298
|
+
return { threadId, runId, createdAt };
|
|
299
|
+
}
|
|
300
|
+
async loadPriorHistory(threadId, runId) {
|
|
301
|
+
const history = await this.persistence.listThreadMessages(threadId);
|
|
302
|
+
return history.filter((message) => message.runId !== runId);
|
|
303
|
+
}
|
|
304
|
+
async appendAssistantMessage(threadId, runId, content) {
|
|
305
|
+
if (!content) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
await this.persistence.appendThreadMessage(threadId, {
|
|
309
|
+
role: "assistant",
|
|
310
|
+
content,
|
|
311
|
+
runId,
|
|
312
|
+
createdAt: new Date().toISOString(),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async invokeWithHistory(binding, input, threadId, runId, resumePayload) {
|
|
316
|
+
const priorHistory = await this.loadPriorHistory(threadId, runId);
|
|
317
|
+
return this.runtimeAdapter.invoke(binding, input, threadId, runId, resumePayload, priorHistory);
|
|
318
|
+
}
|
|
319
|
+
async emitOutputDeltaAndCreateItem(threadId, runId, agentId, content) {
|
|
320
|
+
await this.emit(threadId, runId, 3, "output.delta", {
|
|
321
|
+
content,
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
type: "content",
|
|
325
|
+
threadId,
|
|
326
|
+
runId,
|
|
327
|
+
agentId,
|
|
328
|
+
content,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async emitRunCreated(threadId, runId, payload) {
|
|
332
|
+
return this.emit(threadId, runId, 1, "run.created", payload);
|
|
333
|
+
}
|
|
334
|
+
async setRunStateAndEmit(threadId, runId, sequence, state, options) {
|
|
335
|
+
await this.persistence.setRunState(threadId, runId, state, options.checkpointRef ?? null);
|
|
336
|
+
return this.emit(threadId, runId, sequence, "run.state.changed", {
|
|
337
|
+
previousState: options.previousState,
|
|
338
|
+
state,
|
|
339
|
+
checkpointRef: options.checkpointRef ?? null,
|
|
340
|
+
...(options.error ? { error: options.error } : {}),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async requestApprovalAndEmit(threadId, runId, input, interruptContent, checkpointRef, sequence) {
|
|
344
|
+
const approval = await this.persistApproval(threadId, runId, checkpointRef, input, interruptContent);
|
|
345
|
+
const event = await this.emit(threadId, runId, sequence, "approval.requested", {
|
|
346
|
+
approvalId: approval.approvalId,
|
|
347
|
+
pendingActionId: approval.pendingActionId,
|
|
348
|
+
toolName: approval.toolName,
|
|
349
|
+
toolCallId: approval.toolCallId,
|
|
350
|
+
allowedDecisions: approval.allowedDecisions,
|
|
351
|
+
checkpointRef,
|
|
352
|
+
});
|
|
353
|
+
return { approval, event };
|
|
354
|
+
}
|
|
355
|
+
async emitSyntheticFallback(threadId, runId, selectedAgentId, error, sequence = 3) {
|
|
356
|
+
await this.emit(threadId, runId, sequence, "runtime.synthetic_fallback", {
|
|
357
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
358
|
+
selectedAgentId,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
272
361
|
async persistApproval(threadId, runId, checkpointRef, input, interruptContent) {
|
|
273
362
|
const approval = createPendingApproval(threadId, runId, checkpointRef, input, interruptContent);
|
|
274
363
|
await this.persistence.createApproval(approval);
|
|
@@ -381,68 +470,25 @@ export class AgentHarness {
|
|
|
381
470
|
if (!policyDecision.allowed) {
|
|
382
471
|
throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
|
|
383
472
|
}
|
|
384
|
-
const threadId = options.threadId
|
|
385
|
-
|
|
386
|
-
const createdAt = new Date().toISOString();
|
|
387
|
-
if (!options.threadId) {
|
|
388
|
-
await this.persistence.createThread({
|
|
389
|
-
threadId,
|
|
390
|
-
agentId: selectedAgentId,
|
|
391
|
-
runId,
|
|
392
|
-
status: "running",
|
|
393
|
-
createdAt,
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
await this.persistence.appendThreadMessage(threadId, {
|
|
397
|
-
role: "user",
|
|
398
|
-
content: options.input,
|
|
399
|
-
runId,
|
|
400
|
-
createdAt,
|
|
401
|
-
});
|
|
402
|
-
await this.persistence.createRun({
|
|
403
|
-
threadId,
|
|
404
|
-
runId,
|
|
405
|
-
agentId: binding.agent.id,
|
|
406
|
-
executionMode: binding.agent.executionMode,
|
|
407
|
-
createdAt,
|
|
408
|
-
});
|
|
409
|
-
await this.emit(threadId, runId, 1, "run.created", {
|
|
473
|
+
const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
|
|
474
|
+
await this.emitRunCreated(threadId, runId, {
|
|
410
475
|
agentId: binding.agent.id,
|
|
411
476
|
requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
|
|
412
477
|
selectedAgentId,
|
|
413
478
|
executionMode: binding.agent.executionMode,
|
|
414
479
|
});
|
|
415
480
|
try {
|
|
416
|
-
const
|
|
417
|
-
const priorHistory = history.filter((message) => message.runId !== runId);
|
|
418
|
-
const actual = await this.runtimeAdapter.invoke(binding, options.input, threadId, runId, undefined, priorHistory);
|
|
481
|
+
const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
|
|
419
482
|
let approval;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
content: actual.output,
|
|
424
|
-
runId,
|
|
425
|
-
createdAt: new Date().toISOString(),
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
await this.persistence.setRunState(threadId, runId, actual.state, actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null);
|
|
429
|
-
if (actual.state === "waiting_for_approval") {
|
|
430
|
-
const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
|
|
431
|
-
approval = await this.persistApproval(threadId, runId, checkpointRef, options.input, actual.interruptContent);
|
|
432
|
-
await this.emit(threadId, runId, 4, "approval.requested", {
|
|
433
|
-
approvalId: approval.approvalId,
|
|
434
|
-
pendingActionId: approval.pendingActionId,
|
|
435
|
-
toolName: approval.toolName,
|
|
436
|
-
toolCallId: approval.toolCallId,
|
|
437
|
-
allowedDecisions: approval.allowedDecisions,
|
|
438
|
-
checkpointRef,
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
await this.emit(threadId, runId, 3, "run.state.changed", {
|
|
483
|
+
await this.appendAssistantMessage(threadId, runId, actual.output);
|
|
484
|
+
const checkpointRef = actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null;
|
|
485
|
+
await this.setRunStateAndEmit(threadId, runId, 3, actual.state, {
|
|
442
486
|
previousState: null,
|
|
443
|
-
|
|
444
|
-
checkpointRef: actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null,
|
|
487
|
+
checkpointRef,
|
|
445
488
|
});
|
|
489
|
+
if (actual.state === "waiting_for_approval") {
|
|
490
|
+
approval = (await this.requestApprovalAndEmit(threadId, runId, options.input, actual.interruptContent, checkpointRef, 4)).approval;
|
|
491
|
+
}
|
|
446
492
|
return {
|
|
447
493
|
...actual,
|
|
448
494
|
threadId,
|
|
@@ -453,15 +499,9 @@ export class AgentHarness {
|
|
|
453
499
|
};
|
|
454
500
|
}
|
|
455
501
|
catch (error) {
|
|
456
|
-
await this.
|
|
457
|
-
|
|
458
|
-
selectedAgentId,
|
|
459
|
-
});
|
|
460
|
-
await this.persistence.setRunState(threadId, runId, "failed");
|
|
461
|
-
await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
502
|
+
await this.emitSyntheticFallback(threadId, runId, selectedAgentId, error);
|
|
503
|
+
await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
|
|
462
504
|
previousState: null,
|
|
463
|
-
state: "failed",
|
|
464
|
-
checkpointRef: null,
|
|
465
505
|
error: error instanceof Error ? error.message : String(error),
|
|
466
506
|
});
|
|
467
507
|
return {
|
|
@@ -490,32 +530,8 @@ export class AgentHarness {
|
|
|
490
530
|
return;
|
|
491
531
|
}
|
|
492
532
|
let emitted = false;
|
|
493
|
-
const threadId = options.threadId
|
|
494
|
-
|
|
495
|
-
const createdAt = new Date().toISOString();
|
|
496
|
-
if (!options.threadId) {
|
|
497
|
-
await this.persistence.createThread({
|
|
498
|
-
threadId,
|
|
499
|
-
agentId: selectedAgentId,
|
|
500
|
-
runId,
|
|
501
|
-
status: "running",
|
|
502
|
-
createdAt,
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
await this.persistence.appendThreadMessage(threadId, {
|
|
506
|
-
role: "user",
|
|
507
|
-
content: options.input,
|
|
508
|
-
runId,
|
|
509
|
-
createdAt,
|
|
510
|
-
});
|
|
511
|
-
await this.persistence.createRun({
|
|
512
|
-
threadId,
|
|
513
|
-
runId,
|
|
514
|
-
agentId: selectedAgentId,
|
|
515
|
-
executionMode: binding.agent.executionMode,
|
|
516
|
-
createdAt,
|
|
517
|
-
});
|
|
518
|
-
yield { type: "event", event: await this.emit(threadId, runId, 1, "run.created", {
|
|
533
|
+
const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
|
|
534
|
+
yield { type: "event", event: await this.emitRunCreated(threadId, runId, {
|
|
519
535
|
agentId: selectedAgentId,
|
|
520
536
|
requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
|
|
521
537
|
selectedAgentId,
|
|
@@ -523,8 +539,7 @@ export class AgentHarness {
|
|
|
523
539
|
state: "running",
|
|
524
540
|
}) };
|
|
525
541
|
try {
|
|
526
|
-
const
|
|
527
|
-
const priorHistory = history.filter((message) => message.runId !== runId);
|
|
542
|
+
const priorHistory = await this.loadPriorHistory(threadId, runId);
|
|
528
543
|
let assistantOutput = "";
|
|
529
544
|
for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory)) {
|
|
530
545
|
if (chunk) {
|
|
@@ -535,26 +550,18 @@ export class AgentHarness {
|
|
|
535
550
|
: chunk;
|
|
536
551
|
if (normalizedChunk.kind === "interrupt") {
|
|
537
552
|
const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
|
|
538
|
-
await this.
|
|
539
|
-
|
|
553
|
+
const waitingEvent = await this.setRunStateAndEmit(threadId, runId, 4, "waiting_for_approval", {
|
|
554
|
+
previousState: null,
|
|
555
|
+
checkpointRef,
|
|
556
|
+
});
|
|
557
|
+
const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef, 5);
|
|
540
558
|
yield {
|
|
541
559
|
type: "event",
|
|
542
|
-
event:
|
|
543
|
-
previousState: null,
|
|
544
|
-
state: "waiting_for_approval",
|
|
545
|
-
checkpointRef,
|
|
546
|
-
}),
|
|
560
|
+
event: waitingEvent,
|
|
547
561
|
};
|
|
548
562
|
yield {
|
|
549
563
|
type: "event",
|
|
550
|
-
event:
|
|
551
|
-
approvalId: approval.approvalId,
|
|
552
|
-
pendingActionId: approval.pendingActionId,
|
|
553
|
-
toolName: approval.toolName,
|
|
554
|
-
toolCallId: approval.toolCallId,
|
|
555
|
-
allowedDecisions: approval.allowedDecisions,
|
|
556
|
-
checkpointRef,
|
|
557
|
-
}),
|
|
564
|
+
event: approvalRequest.event,
|
|
558
565
|
};
|
|
559
566
|
return;
|
|
560
567
|
}
|
|
@@ -594,98 +601,46 @@ export class AgentHarness {
|
|
|
594
601
|
}
|
|
595
602
|
emitted = true;
|
|
596
603
|
assistantOutput += normalizedChunk.content;
|
|
597
|
-
await this.
|
|
598
|
-
content: normalizedChunk.content,
|
|
599
|
-
});
|
|
600
|
-
yield {
|
|
601
|
-
type: "content",
|
|
602
|
-
threadId,
|
|
603
|
-
runId,
|
|
604
|
-
agentId: selectedAgentId,
|
|
605
|
-
content: normalizedChunk.content,
|
|
606
|
-
};
|
|
604
|
+
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
|
|
607
605
|
}
|
|
608
606
|
}
|
|
609
607
|
if (!assistantOutput) {
|
|
610
|
-
const actual = await this.
|
|
608
|
+
const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
|
|
611
609
|
if (actual.output) {
|
|
612
610
|
assistantOutput = actual.output;
|
|
613
611
|
emitted = true;
|
|
614
|
-
await this.
|
|
615
|
-
content: actual.output,
|
|
616
|
-
});
|
|
617
|
-
yield {
|
|
618
|
-
type: "content",
|
|
619
|
-
threadId,
|
|
620
|
-
runId,
|
|
621
|
-
agentId: selectedAgentId,
|
|
622
|
-
content: actual.output,
|
|
623
|
-
};
|
|
612
|
+
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
|
|
624
613
|
}
|
|
625
614
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
role: "assistant",
|
|
629
|
-
content: assistantOutput,
|
|
630
|
-
runId,
|
|
631
|
-
createdAt: new Date().toISOString(),
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
await this.persistence.setRunState(threadId, runId, "completed");
|
|
635
|
-
yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
615
|
+
await this.appendAssistantMessage(threadId, runId, assistantOutput);
|
|
616
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
|
|
636
617
|
previousState: null,
|
|
637
|
-
state: "completed",
|
|
638
|
-
checkpointRef: null,
|
|
639
618
|
}) };
|
|
640
619
|
return;
|
|
641
620
|
}
|
|
642
621
|
catch (error) {
|
|
643
622
|
if (emitted) {
|
|
644
|
-
await this.
|
|
645
|
-
yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
623
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
|
|
646
624
|
previousState: null,
|
|
647
|
-
state: "failed",
|
|
648
625
|
error: error instanceof Error ? error.message : String(error),
|
|
649
626
|
}) };
|
|
650
627
|
return;
|
|
651
628
|
}
|
|
652
629
|
try {
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
const actual = await this.runtimeAdapter.invoke(binding, options.input, threadId, runId, undefined, priorHistory);
|
|
630
|
+
const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
|
|
631
|
+
await this.appendAssistantMessage(threadId, runId, actual.output);
|
|
656
632
|
if (actual.output) {
|
|
657
|
-
await this.
|
|
658
|
-
role: "assistant",
|
|
659
|
-
content: actual.output,
|
|
660
|
-
runId,
|
|
661
|
-
createdAt: new Date().toISOString(),
|
|
662
|
-
});
|
|
663
|
-
yield {
|
|
664
|
-
type: "content",
|
|
665
|
-
threadId,
|
|
666
|
-
runId,
|
|
667
|
-
agentId: selectedAgentId,
|
|
668
|
-
content: actual.output,
|
|
669
|
-
};
|
|
633
|
+
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
|
|
670
634
|
}
|
|
671
|
-
await this.
|
|
672
|
-
yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
635
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
|
|
673
636
|
previousState: null,
|
|
674
|
-
state: actual.state,
|
|
675
|
-
checkpointRef: null,
|
|
676
637
|
}) };
|
|
677
638
|
return;
|
|
678
639
|
}
|
|
679
640
|
catch (invokeError) {
|
|
680
|
-
await this.
|
|
681
|
-
|
|
682
|
-
selectedAgentId,
|
|
683
|
-
});
|
|
684
|
-
await this.persistence.setRunState(threadId, runId, "failed");
|
|
685
|
-
yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
641
|
+
await this.emitSyntheticFallback(threadId, runId, selectedAgentId, invokeError);
|
|
642
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
|
|
686
643
|
previousState: null,
|
|
687
|
-
state: "failed",
|
|
688
|
-
checkpointRef: null,
|
|
689
644
|
error: invokeError instanceof Error ? invokeError.message : String(invokeError),
|
|
690
645
|
}) };
|
|
691
646
|
yield {
|
|
@@ -737,14 +692,7 @@ export class AgentHarness {
|
|
|
737
692
|
? { decision: "edit", editedInput: options.editedInput }
|
|
738
693
|
: (options.decision ?? "approve");
|
|
739
694
|
const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumeDecision, priorHistory);
|
|
740
|
-
|
|
741
|
-
await this.persistence.appendThreadMessage(threadId, {
|
|
742
|
-
role: "assistant",
|
|
743
|
-
content: actual.output,
|
|
744
|
-
runId,
|
|
745
|
-
createdAt: new Date().toISOString(),
|
|
746
|
-
});
|
|
747
|
-
}
|
|
695
|
+
await this.appendAssistantMessage(threadId, runId, actual.output);
|
|
748
696
|
await this.persistence.setRunState(threadId, runId, actual.state, actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null);
|
|
749
697
|
await this.emit(threadId, runId, 7, "run.state.changed", {
|
|
750
698
|
previousState: "resuming",
|